=PHPStorm Code Inspection: fixed class reference errors (from getting rid of JVB() and replacing with direct class::getInstance; as well as general code sniffing, etc
242 files modified
1 files added
| | |
| | | <?php |
| | | namespace JVBase; |
| | | |
| | | use JVBase\admin\SEOAdmin; |
| | | use JVBase\base\SchemaHelper; |
| | | use JVBase\base\Site; |
| | | use JVBase\blocks\CustomBlocks; |
| | | use JVBase\managers\Approvals; |
| | | use JVBase\managers\DirectoryManager; |
| | | use JVBase\managers\Favourites; |
| | | use JVBase\managers\InvitationsManager; |
| | | use JVBase\managers\Login; |
| | | use JVBase\managers\NotificationManager; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\managers\Referrals; |
| | | use JVBase\managers\SEO\render\SchemaOutput; |
| | | use JVBase\managers\TaxonomyRelationships; |
| | | use JVBase\managers\UserTermsManager; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\routes\AdminRoutes; |
| | | use JVBase\rest\routes\ApprovalRoutes; |
| | | use JVBase\rest\routes\ContentRoutes; |
| | | use JVBase\rest\routes\ContentTermsRoutes; |
| | | use JVBase\rest\routes\ErrorRoutes; |
| | | use JVBase\rest\routes\FavouritesRoutes; |
| | | use JVBase\rest\routes\FeedRoutes; |
| | | use JVBase\rest\routes\FormRoutes; |
| | | use JVBase\rest\routes\Invitations; |
| | | use JVBase\rest\routes\LoginRoutes; |
| | | use JVBase\rest\routes\NewsRoutes; |
| | | use JVBase\rest\routes\NotificationsRoutes; |
| | | use JVBase\rest\routes\OptionsRoutes; |
| | | use JVBase\rest\routes\ReferralRoutes; |
| | | use JVBase\rest\routes\ResponseRoutes; |
| | | use JVBase\rest\routes\SEORoutes; |
| | | use JVBase\rest\routes\SettingsRoutes; |
| | | use JVBase\rest\routes\TermRoutes; |
| | | use JVBase\rest\routes\UploadRoutes; |
| | | use JVBase\rest\routes\VoteRoutes; |
| | | use UserRoutes; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\CustomTable; |
| | | use JVBase\base\Site; |
| | | use JVBase\managers\DashboardManager; |
| | | use JVBase\managers\DirectoryManager; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\managers\Referrals; |
| | | use JVBase\managers\Roles; |
| | | use JVBase\registrar\Registrar; |
| | |
| | | ]; |
| | | |
| | | foreach ($capabilities as $cap) { |
| | | $role->add_cap($cap, true); |
| | | $role->add_cap($cap); |
| | | } |
| | | |
| | | foreach ($users as $user) { |
| | |
| | | // Remove third-party styles |
| | | wp_deregister_style('akismet-widget-style-inline-css'); |
| | | } |
| | | add_action('wp_enqueue_scripts', 'jvbRemoveBlockAssets', 9999); |
| | | //add_action('wp_enqueue_scripts', 'jvbRemoveBlockAssets', 9999); |
| | | |
| | | /******************************************************************************* |
| | | WORDPRESS HEAD CLEANUP |
| | |
| | | } |
| | | } |
| | | } |
| | | add_action('before_delete_post', 'jvbCleanupPostDeletion'); |
| | | //add_action('before_delete_post', 'jvbCleanupPostDeletion'); |
| | | |
| | | /******************************************************************************* |
| | | THEME CUSTOMIZATION |
| | |
| | | function jvbBodyClasses(array $classes): array |
| | | { |
| | | // Start with empty array - only add what we need |
| | | $clean_classes = []; |
| | | $classes = []; |
| | | |
| | | // Add contextual classes |
| | | if (is_front_page()) { |
| | | $clean_classes[] = 'home'; |
| | | $classes[] = 'home'; |
| | | } |
| | | |
| | | if (function_exists('jvbIsDirectory') && jvbIsDirectory()) { |
| | | $clean_classes[] = 'is-directory'; |
| | | $classes[] = 'is-directory'; |
| | | } elseif (is_tax()) { |
| | | $clean_classes[] = str_replace(BASE, '', get_queried_object()->taxonomy); |
| | | $classes[] = str_replace(BASE, '', get_queried_object()->taxonomy); |
| | | } elseif (is_singular() && !is_singular('page')) { |
| | | $clean_classes[] = str_replace(BASE, '', get_queried_object()->post_type); |
| | | $classes[] = str_replace(BASE, '', get_queried_object()->post_type); |
| | | } elseif (is_post_type_archive()) { |
| | | $clean_classes[] = str_replace(BASE, '', get_queried_object()->name); |
| | | $classes[] = str_replace(BASE, '', get_queried_object()->name); |
| | | } |
| | | |
| | | return $clean_classes; |
| | | return $classes; |
| | | } |
| | | add_filter('body_class', 'jvbBodyClasses'); |
| | | |
| | |
| | | |
| | | // Hide existing comments |
| | | add_filter('comments_array', '__return_empty_array', 10, 2); |
| | | |
| | | // Remove comments page in menu |
| | | add_action('admin_menu', function () { |
| | | remove_menu_page('edit-comments.php'); |
| | | }); |
| | | |
| | | // Remove comments links from admin bar |
| | | add_action('init', function () { |
| | | if (is_admin_bar_showing()) { |
| | | remove_action('admin_bar_menu', 'wp_admin_bar_comments_menu', 60); |
| | | } |
| | | }); |
| | | // Remove comments links from admin bar |
| | | add_action('add_admin_bar_menus', function () { |
| | | remove_action('admin_bar_menu', 'wp_admin_bar_comments_menu', 60); |
| | | }); |
| | | // |
| | | //// Remove comments page in menu |
| | | //add_action('admin_menu', function () { |
| | | // remove_menu_page('edit-comments.php'); |
| | | //}); |
| | | // |
| | | //// Remove comments links from admin bar |
| | | //add_action('init', function () { |
| | | // if (is_admin_bar_showing()) { |
| | | // remove_action('admin_bar_menu', 'wp_admin_bar_comments_menu', 60); |
| | | // } |
| | | //}); |
| | | //// Remove comments links from admin bar |
| | | //add_action('add_admin_bar_menus', function () { |
| | | // remove_action('admin_bar_menu', 'wp_admin_bar_comments_menu', 60); |
| | | //}); |
| | |
| | | exit; |
| | | } |
| | | |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Error; |
| | | use Exception; |
| | |
| | | |
| | | public function processOperation(WP_Error $result, object $operation, array $data):WP_Error|array |
| | | { |
| | | switch ($operation->type) { |
| | | case 'sync_content_taxonomy_tables': |
| | | return $this->processContentTaxonomyTableSync($operation, $data); |
| | | default: |
| | | return $result; |
| | | } |
| | | return match ($operation->type) { |
| | | 'sync_content_taxonomy_tables' => $this->processContentTaxonomyTableSync($operation, $data), |
| | | default => $result, |
| | | }; |
| | | } |
| | | |
| | | public function processContentTaxonomyTableSync(object $operation, array $data):array |
| | | { |
| | | //TODO: This won't work currently |
| | | $taxonomy = $data['taxonomy']; |
| | | $registrar = new TaxonomyRegistrar($taxonomy, JVB_TAXONOMY[$taxonomy]); |
| | | $registrar = Registrar::getInstance($taxonomy); |
| | | $results = ['errors' => 0, 'success' => 0]; |
| | | foreach ($data['terms'] as $term) { |
| | | try { |
| | |
| | | <?php |
| | | namespace JVBase\admin; |
| | | |
| | | use JVBase\base\Site; |
| | | use JVBase\integrations\ServiceMap; |
| | | use JVBase\managers\IconsManager; |
| | | use JVBase\registrar\Registrar; |
| | |
| | | } |
| | | public function prerender(?string $content, array $block, ?WP_Block $parent = null):?string |
| | | { |
| | | $result = $this->checkMethods($content, $block, $parent, true); |
| | | return $result; |
| | | return $this->checkMethods($content, $block, $parent, true); |
| | | } |
| | | |
| | | public function render(string $content, array $block):string |
| | |
| | | } |
| | | if (array_key_exists('focalPoint', $attrs)) { |
| | | $attrs['isObjectPosition'] = true; |
| | | $style = $this->getFocalPointStyle($attrs['focalPoint'], $attrs); |
| | | $style = implode(';',$this->getFocalPointStyle($attrs['focalPoint'], $attrs)); |
| | | $style .= ';object-fit:none;'; |
| | | $imgLink = str_replace('<img', sprintf( |
| | | '<img style="%s"', |
| | |
| | | <?php |
| | | namespace JVBase\blocks; |
| | | |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\DirectoryManager; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Block; |
| | |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\base\Site; |
| | | use JVBase\forms\TaxonomySelector; |
| | | use JVBase\ui\CRUDSkeleton; |
| | | use WP_Block; |
| | | use WP_Query; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | if (!$isTemplate && empty($value)) { |
| | | return ''; |
| | | } |
| | | switch ($fieldName) { |
| | | case 'post_title': |
| | | return sprintf( |
| | | '<h3%s>%s</h3>', |
| | | $data, |
| | | $meta && !empty($value) ? $value : '', |
| | | ); |
| | | case 'post_date': |
| | | case 'post_modified': |
| | | return sprintf( |
| | | '<time%s%s>%s</time>', |
| | | $data, |
| | | $meta && !empty($value) ? date('c', strtotime($value)) : '', |
| | | $meta && !empty($value) ? date('F j, Y', strtotime($value)) : '', |
| | | ); |
| | | } |
| | | |
| | | return match ($fieldType) { |
| | | 'date' => sprintf( |
| | | return match ($fieldName) { |
| | | 'post_title' => sprintf( |
| | | '<h3%s>%s</h3>', |
| | | $data, |
| | | $meta && !empty($value) ? $value : '', |
| | | ), |
| | | 'post_date', 'post_modified' => sprintf( |
| | | '<time%s%s>%s</time>', |
| | | $data, |
| | | $meta && !empty($value) ? date('c', strtotime($value)) : '', |
| | | $meta && !empty($value) ? date('F j, Y', strtotime($value)) : '', |
| | | ), |
| | | 'datetime' => sprintf( |
| | | '<time%s%s>%s</time>', |
| | | $data, |
| | | $meta && !empty($value) ? date('c', strtotime($value)) : '', |
| | | $meta && !empty($value) ? date('F j, Y at h:iA', strtotime($value)) : '', |
| | | ), |
| | | 'time' => sprintf( |
| | | '<time%s%s>%s</time>', |
| | | $data, |
| | | $value, |
| | | $value |
| | | ), |
| | | 'upload' => empty($value) ? sprintf( |
| | | '<img%s width="300px" height="300px" loading="lazy" decoding="async">', |
| | | $data |
| | | ) : |
| | | str_replace('<img', '<img' . $data, jvbFormatImage($value)), |
| | | 'selector' => sprintf( |
| | | '<ul%s class="terms %s">%s</ul>', |
| | | $data, |
| | | $config['taxonomy']??$config['post_type']??$config['role']??'', |
| | | empty($value) ? sprintf('<li><a>%s</a></li>', |
| | | $this->iconFor($config) |
| | | default => match ($fieldType) { |
| | | 'date' => sprintf( |
| | | '<time%s%s>%s</time>', |
| | | $data, |
| | | $meta && !empty($value) ? date('c', strtotime($value)) : '', |
| | | $meta && !empty($value) ? date('F j, Y', strtotime($value)) : '', |
| | | ), |
| | | 'datetime' => sprintf( |
| | | '<time%s%s>%s</time>', |
| | | $data, |
| | | $meta && !empty($value) ? date('c', strtotime($value)) : '', |
| | | $meta && !empty($value) ? date('F j, Y at h:iA', strtotime($value)) : '', |
| | | ), |
| | | 'time' => sprintf( |
| | | '<time%s%s>%s</time>', |
| | | $data, |
| | | $value, |
| | | $value |
| | | ), |
| | | 'upload' => empty($value) ? sprintf( |
| | | '<img%s width="300px" height="300px" loading="lazy" decoding="async">', |
| | | $data |
| | | ) : |
| | | implode('', array_filter(array_map(function ($ID) use ($config) { |
| | | $type = $config['subtype']; |
| | | $taxonomy = isset($config['taxonomy']) ? jvbCheckBase($config['taxonomy']) : ''; |
| | | str_replace('<img', '<img' . $data, jvbFormatImage($value)), |
| | | 'selector' => sprintf( |
| | | '<ul%s class="terms %s">%s</ul>', |
| | | $data, |
| | | $config['taxonomy'] ?? $config['post_type'] ?? $config['role'] ?? '', |
| | | empty($value) ? sprintf('<li><a>%s</a></li>', |
| | | $this->iconFor($config) |
| | | ) : |
| | | implode('', array_filter(array_map(function ($ID) use ($config) { |
| | | $type = $config['subtype']; |
| | | $taxonomy = isset($config['taxonomy']) ? jvbCheckBase($config['taxonomy']) : ''; |
| | | |
| | | $item = match ($type) { |
| | | 'taxonomy' => get_term($ID, $taxonomy), |
| | | 'user' => get_userdata($ID), |
| | | 'post' => get_post($ID), |
| | | default => false, |
| | | }; |
| | | $item = match ($type) { |
| | | 'taxonomy' => get_term($ID, $taxonomy), |
| | | 'user' => get_userdata($ID), |
| | | 'post' => get_post($ID), |
| | | default => false, |
| | | }; |
| | | |
| | | if (!$item || is_wp_error($item)) { |
| | | return ''; |
| | | } |
| | | if (!$item || is_wp_error($item)) { |
| | | return ''; |
| | | } |
| | | |
| | | $icon = $this->iconFor($config); |
| | | $icon = $this->iconFor($config); |
| | | |
| | | $name = match ($type) { |
| | | 'taxonomy' => $item->name, |
| | | 'user' => $item->display_name, |
| | | 'post' => $item->post_title, |
| | | default => '', |
| | | }; |
| | | $url = match ($type) { |
| | | 'taxonomy' => get_term_link((int)$ID, $taxonomy), |
| | | 'user' => get_the_permalink(get_user_meta($ID, BASE . 'userLink', true)), |
| | | 'post' => get_the_permalink($ID), |
| | | default => '' |
| | | }; |
| | | return sprintf( |
| | | '<li><a href="%s">%s%s</a></li>', |
| | | $url, |
| | | !empty($icon) ? jvbIcon($icon) : '', |
| | | $name |
| | | ); |
| | | }, explode(',', $value)))) |
| | | ), |
| | | default => sprintf( |
| | | '<p%s>%s</p>', |
| | | $data, |
| | | $value |
| | | ), |
| | | $name = match ($type) { |
| | | 'taxonomy' => $item->name, |
| | | 'user' => $item->display_name, |
| | | 'post' => $item->post_title, |
| | | default => '', |
| | | }; |
| | | $url = match ($type) { |
| | | 'taxonomy' => get_term_link((int)$ID, $taxonomy), |
| | | 'user' => get_the_permalink(get_user_meta($ID, BASE . 'userLink', true)), |
| | | 'post' => get_the_permalink($ID), |
| | | default => '' |
| | | }; |
| | | return sprintf( |
| | | '<li><a href="%s">%s%s</a></li>', |
| | | $url, |
| | | !empty($icon) ? jvbIcon($icon) : '', |
| | | $name |
| | | ); |
| | | }, explode(',', $value)))) |
| | | ), |
| | | default => sprintf( |
| | | '<p%s>%s</p>', |
| | | $data, |
| | | $value |
| | | ), |
| | | }, |
| | | }; |
| | | |
| | | } |
| | | protected function iconFor(array $fieldConfig):string |
| | | { |
| | |
| | | protected function defaultFieldTemplate(string $fieldType, string $fieldName):string |
| | | { |
| | | $data = ' data-field="'.$fieldName.'"'; |
| | | switch ($fieldName) { |
| | | case 'post_title': |
| | | return '<h3'.$data.'></h3>'; |
| | | case 'post_date': |
| | | case 'post_modified': |
| | | return '<time'.$data.'></time>'; |
| | | } |
| | | return match($fieldType) { |
| | | 'upload' => '<img'.$data.' width="300px" height="300px" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px" loading="lazy" decoding="async">', |
| | | 'taxonomy' => '<ul'.$data.'><li><a><i></i></a></li></ul>', |
| | | default => '<p'.$data.'></p>', |
| | | return match ($fieldName) { |
| | | 'post_title' => '<h3' . $data . '></h3>', |
| | | 'post_date', 'post_modified' => '<time' . $data . '></time>', |
| | | default => match ($fieldType) { |
| | | 'upload' => '<img' . $data . ' width="300px" height="300px" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px" loading="lazy" decoding="async">', |
| | | 'taxonomy' => '<ul' . $data . '><li><a><i></i></a></li></ul>', |
| | | default => '<p' . $data . '></p>', |
| | | }, |
| | | }; |
| | | } |
| | | /** |
| | |
| | | $forms = apply_filters('jvb_register_forms', $default_forms); |
| | | |
| | | // Process forms to ensure they have proper structure |
| | | $processed_forms = []; |
| | | foreach ($forms as $form_key => $form_config) { |
| | | $processed_forms[$form_key] = $this->processFormConfig($form_config); |
| | | } |
| | | $processed_forms = array_map(function ($form_config) { |
| | | return $this->processFormConfig($form_config); |
| | | }, $forms); |
| | | |
| | | return $processed_forms; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\blocks; |
| | | |
| | | use Exception; |
| | | use JVBase\base\Site; |
| | | use JVBase\integrations\services\GoogleMyBusiness; |
| | | //use JVBase\managers\Cache; |
| | |
| | | <?php |
| | | return ob_get_clean(); |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | error_log('[GMB Reviews Block] Error: ' . $e->getMessage()); |
| | | return ''; |
| | | } |
| | |
| | | use JVBase\base\Options; |
| | | use JVBase\base\Site; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\forms\TaxonomySelector; |
| | | use JVBase\meta\Form; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\meta\Render; |
| | | use WP_Block; |
| | | use WP_Query; |
| | | |
| | |
| | | data-quantity="1" |
| | | data-product_id="%d" |
| | | data-variation_id="%d" |
| | | class="add_to_cart_button ajax_add_to_cart add-to-cart"> |
| | | class="btn chip add_to_cart_button ajax_add_to_cart add-to-cart"> |
| | | %s |
| | | <span>Add to Cart</span> |
| | | </a>', //Note: we had 3 %s%s%s before the last </div> last time. This was to add optional ingredients or preparation time duration, not default in WooCommerce |
| | |
| | | '<a href="?add-to-cart=%d" |
| | | data-quantity="1" |
| | | data-product_id="%d" |
| | | class="add_to_cart_button ajax_add_to_cart add-to-cart"> |
| | | class="btn chip add_to_cart_button ajax_add_to_cart add-to-cart"> |
| | | %s |
| | | <span>Add to Cart</span> |
| | | </a>', |
| | |
| | | namespace JVBase\blocks; |
| | | |
| | | use JVBase\managers\Cache; |
| | | use JVBase\forms\TaxonomySelector; |
| | | use WP_Block; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | |
| | | public function renderGallery():void |
| | | { |
| | | jvbRenderGallery(true); |
| | | jvbRenderGallery(); |
| | | } |
| | | } |
| | |
| | | <?php |
| | | |
| | | use JVBase\base\Site; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | * Use BreadcrumbManager directly for new code |
| | | */ |
| | | |
| | | use JVBase\managers\DirectoryManager; |
| | | use JVBase\managers\SEO\BreadcrumbManager; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | <?php |
| | | |
| | | use JVBase\managers\Roles; |
| | | use JVBase\registrar\Registrar; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | * Checks if the logged in user is one of us |
| | | * @return bool |
| | | */ |
| | | function isOurPeople():bool |
| | | function isOurPeople(?int $userID = null):bool |
| | | { |
| | | if (!is_user_logged_in()) { |
| | | if (is_null($userID) && !is_user_logged_in()) { |
| | | return false; |
| | | } |
| | | $user = wp_get_current_user(); |
| | | $user = is_null($userID) ? wp_get_current_user() : get_userdata($userID); |
| | | if ($user || is_wp_error($user)) { |
| | | return false; |
| | | } |
| | | $registered = Registrar::withFeature('has_dashboard', 'user'); |
| | | $mapped = array_map(fn($role) => BASE.$role,$registered); |
| | | return count( |
| | |
| | | exit; |
| | | } |
| | | |
| | | function jvbRenderForm(string $endpoint, array $fields, array $options = [], bool $return = false):mixed |
| | | function jvbRenderForm(string $endpoint, array $fields, array $options = [], bool $return = false): string|bool |
| | | { |
| | | ob_start(); |
| | | ?> |
| | |
| | | $out = '<ul class="contact">'; |
| | | foreach ($links as $link) { |
| | | $url = $link['url']; |
| | | switch (true) { |
| | | case strpos($url, 'facebook'): |
| | | $i = jvbIcon('facebook-logo'); |
| | | break; |
| | | case strpos($url, 'instagram'): |
| | | $i = jvbIcon('instagram-logo'); |
| | | break; |
| | | case strpos($url, 'tiktok'): |
| | | $i = jvbIcon('tiktok-logo'); |
| | | break; |
| | | case strpos($url, 'x.com'): |
| | | $i = jvbIcon('x'); |
| | | break; |
| | | case strpos($url, 'youtube'): |
| | | $i = jvbIcon('youtube-logo'); |
| | | break; |
| | | case strpos($url, 'mastadon'): |
| | | $i = jvbIcon('mastadon-logo'); |
| | | break; |
| | | case strpos($url, 'fediverse'): |
| | | case strpos($url, 'bluesky'): |
| | | $i = jvbIcon('fediverse-logo'); |
| | | break; |
| | | case strpos($url, 'linktree'): |
| | | $i = jvbIcon('linktree-logo'); |
| | | break; |
| | | case strpos($url, 'snapchat'): |
| | | $i = jvbIcon('snapchat-logo'); |
| | | break; |
| | | case strpos($url, 'twitch'): |
| | | $i = jvbIcon('twitch-logo'); |
| | | break; |
| | | case strpos($url, 'threads'): |
| | | $i = jvbIcon('threads-logo'); |
| | | break; |
| | | case strpos($url, 'twitter'): |
| | | $i = jvbIcon('twitter-logo'); |
| | | break; |
| | | default: |
| | | $i = jvbIcon('link'); |
| | | break; |
| | | } |
| | | $i = match (true) { |
| | | strpos($url, 'facebook') => jvbIcon('facebook-logo'), |
| | | strpos($url, 'instagram') => jvbIcon('instagram-logo'), |
| | | strpos($url, 'tiktok') => jvbIcon('tiktok-logo'), |
| | | strpos($url, 'x.com') => jvbIcon('x'), |
| | | strpos($url, 'youtube') => jvbIcon('youtube-logo'), |
| | | strpos($url, 'mastadon') => jvbIcon('mastadon-logo'), |
| | | strpos($url, 'fediverse'), strpos($url, 'bluesky') => jvbIcon('fediverse-logo'), |
| | | strpos($url, 'linktree') => jvbIcon('linktree-logo'), |
| | | strpos($url, 'snapchat') => jvbIcon('snapchat-logo'), |
| | | strpos($url, 'twitch') => jvbIcon('twitch-logo'), |
| | | strpos($url, 'threads') => jvbIcon('threads-logo'), |
| | | strpos($url, 'twitter') => jvbIcon('twitter-logo'), |
| | | default => jvbIcon('link'), |
| | | }; |
| | | $tracker = (array_key_exists('tracker', $link)) ? '?'.$link['tracker'] : '?source=edmontonInk'; |
| | | $out .= '<li><a href="'.$link['url'].$tracker.'" target="_blank">'.$i.$link['title'].'</a></li>'; |
| | | } |
| | |
| | | $aClose = '</a>'; |
| | | } |
| | | if ($review['date'] !== '') { |
| | | $date = new Date('M j, Y', strtotime($review['date'])); |
| | | $date = date('M j, Y', strtotime($review['date'])); |
| | | } |
| | | |
| | | $out .= '<cite class="row x-btw">'; |
| | |
| | | $out = ''; |
| | | |
| | | if (!empty($hours) && jvbHasOperatingHours($hours)) { |
| | | $out = jvbGetFormattedHours($hours, true, true); |
| | | $out = jvbGetFormattedHours($hours); |
| | | } else { |
| | | $out = '<p class="no-hours">Hours not available</p>'; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\importers; |
| | | |
| | | use Exception; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | // Commit transaction |
| | | $this->wpdb->query('COMMIT'); |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | // Rollback on error |
| | | $this->wpdb->query('ROLLBACK'); |
| | | fclose($handle); |
| | |
| | | <?php |
| | | namespace JVBase\importers; |
| | | |
| | | use Exception; |
| | | use WP_Error; |
| | | use JVBase\managers\Referrals; |
| | | |
| | |
| | | // Commit transaction |
| | | $this->wpdb->query('COMMIT'); |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | // Rollback on error |
| | | $this->wpdb->query('ROLLBACK'); |
| | | fclose($handle); |
| | |
| | | namespace JVBase\integrations; |
| | | |
| | | use Closure; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\meta\Validator; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | |
| | | use Exception; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\managers\UploadManager; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Error; |
| | |
| | | |
| | | return $this->response(false, 'Failed to save credentials to database'); |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | // Revert to old credentials on any error |
| | | $old = Auth::getInstance()->getCredentials( |
| | | $this->service_name, |
| | |
| | | 'message' => 'Connection established successfully' |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => 'Error: ' . $e->getMessage() |
| | |
| | | return $jpegVersion; |
| | | } |
| | | $uploader = UploadManager::getInstance(); |
| | | $converted = $uploader->convertImageTo($imgID, 'jpeg', 80, false); |
| | | $converted = $uploader->convertImageTo($imgID, 'jpeg'); |
| | | if ($converted && !is_wp_error($converted)) { |
| | | update_post_meta($imgID, BASE . 'jpeg_version', $converted['attachment_id']); |
| | | return $converted['attachment_id']; |
| | |
| | | <?php |
| | | namespace JVBase\integrations; |
| | | |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | * Child classes inspect the wp_remote_request response, |
| | | * decode it, and handle/log errors as needed |
| | | */ |
| | | abstract protected function handleResponse(array|\WP_Error $response, string $method, string $endpoint): array; |
| | | abstract protected function handleResponse(array|WP_Error $response, string $method, string $endpoint): array; |
| | | |
| | | protected function initializeRequestLimiter():void |
| | | { |
| | |
| | | return []; |
| | | } |
| | | if ($this->requestLimiter && !$this->awaitRateLimit($endpoint)) { |
| | | $error = new \WP_Error('rate_limit_exceeded', "Rate limit exceeded for {$endpoint}"); |
| | | $error = new WP_Error('rate_limit_exceeded', "Rate limit exceeded for {$endpoint}"); |
| | | return $this->handleResponse($error, $method, $endpoint); |
| | | } |
| | | |
| | |
| | | |
| | | use Exception; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\meta\Sanitizer; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Error; |
| | | use WP_Post; |
| | | use WP_Term; |
| | | use WP_User; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | } |
| | | public function removeSavePost():void |
| | | { |
| | | remove_action('save_post', [$this, 'handleSavePost'], 20, 3); |
| | | remove_action('save_post', [$this, 'handleSavePost'], 20); |
| | | } |
| | | public function handleSavePost(int $postID, WP_Post $post, bool $update):void |
| | | { |
| | |
| | | } |
| | | public function removeDeletePost():void |
| | | { |
| | | remove_action('before_delete_post', [$this, 'handleSavePost'], 20, 3); |
| | | remove_action('before_delete_post', [$this, 'handleSavePost'], 20); |
| | | } |
| | | public function handleDeletePost(int $postID):void |
| | | { |
| | |
| | | <?php |
| | | namespace JVBase\integrations; |
| | | |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\Registrar; |
| | | |
| | |
| | | if (empty($this->syncTaxonomies)) { |
| | | return; |
| | | } |
| | | remove_action('saved_term', [$this, 'handleSaveTerm'], 20, 3); |
| | | remove_action('saved_term', [$this, 'handleSaveTerm'], 20); |
| | | } |
| | | protected function handleSaveTerm(int $termID, int $tt_id, string $taxonomy, bool $update, array $args):void |
| | | { |
| | |
| | | <?php |
| | | namespace JVBase\integrations; |
| | | |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\meta\Sanitizer; |
| | | use JVBase\registrar\Registrar; |
| | |
| | | |
| | | protected function initializeRateLimiters():void |
| | | { |
| | | $key = $this->service_name; |
| | | if (!is_null($this->userID)) { |
| | | $key .= '_'.$this->userID; |
| | | } |
| | | if (method_exists($this, 'initializeRequestLimiter')) { |
| | | $this->initializeRequestLimiter(); |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\integrations\services; |
| | | use Exception; |
| | | use JVBase\integrations\Auth; |
| | | use JVBase\integrations\Integrations; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | return 'https://bsky.social/xrpc/'; |
| | | } |
| | | |
| | | protected function processIntegrationAction(string $action, array $data):\WP_Error|array |
| | | protected function processIntegrationAction(string $action, array $data): WP_Error|array |
| | | { |
| | | return []; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\integrations\services; |
| | | use Exception; |
| | | use JVBase\integrations\Auth; |
| | | use JVBase\integrations\Integrations; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | return 'https://bsky.social/xrpc/'; |
| | | } |
| | | |
| | | protected function processIntegrationAction(string $action, array $data):\WP_Error|array |
| | | protected function processIntegrationAction(string $action, array $data): WP_Error|array |
| | | { |
| | | return []; |
| | | } |
| | |
| | | use JVBase\integrations\SyncFrom; |
| | | use JVBase\integrations\SyncTo; |
| | | use JVBase\integrations\UserConnection; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\meta\Sanitizer; |
| | | use WP_Error; |
| | | |
| | |
| | | */ |
| | | |
| | | namespace JVBase\integrations\services; |
| | | use Exception; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\meta\Meta; |
| | | use WP_Post; |
| | |
| | | private string $page_id = ''; |
| | | private string $page_access_token = ''; |
| | | private array $permissions = []; |
| | | private array $pages = []; |
| | | |
| | | // Post type mapping for Facebook |
| | | private const FB_POST_TYPES = [ |
| | | 'post' => 'feed', |
| | | 'photo' => 'photos', |
| | | 'video' => 'videos', |
| | | 'event' => 'events', |
| | | 'offer' => 'offers', |
| | | 'note' => 'notes', |
| | | 'milestone' => 'milestones' |
| | | ]; |
| | | |
| | | protected static array $instances = []; |
| | | public static function getInstance(?int $userID = null):self |
| | |
| | | { |
| | | try { |
| | | $fb = (array_key_exists('user', $data) && $data['user'] !== 0) ? $this->getInstance((int)$data['user']) : $this; |
| | | switch ($operation->type) { |
| | | case 'facebook_create_post': |
| | | return $fb->createFacebookPost($data); |
| | | |
| | | case 'facebook_update_post': |
| | | return $fb->updateFacebookPost($data); |
| | | |
| | | case 'facebook_delete_post': |
| | | return $fb->deleteFacebookPost($data); |
| | | |
| | | case 'facebook_create_event': |
| | | return $fb->createFacebookEvent($data); |
| | | |
| | | default: |
| | | return $result; |
| | | } |
| | | return match ($operation->type) { |
| | | 'facebook_create_post' => $fb->createFacebookPost($data), |
| | | 'facebook_update_post' => $fb->updateFacebookPost($data), |
| | | 'facebook_delete_post' => $fb->deleteFacebookPost($data), |
| | | 'facebook_create_event' => $fb->createFacebookEvent($data), |
| | | default => $result, |
| | | }; |
| | | } catch (Exception $e) { |
| | | $this->logError('Operation failed', [ |
| | | 'type' => $operation->type, |
| | |
| | | use JVBase\Instance; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\integrations\Requests; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | namespace JVBase\integrations\services; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | private string $public_key; |
| | | private string $private_key; |
| | | private string $map_id; |
| | | private array $enabled_apis = []; |
| | | |
| | | // Edmonton default coordinates |
| | | private const DEFAULT_LAT = 53.5461; |
| | |
| | | { |
| | | $theme = $this->credentials['theme'] ?? 'default'; |
| | | |
| | | switch ($theme) { |
| | | case 'dark': |
| | | return [ |
| | | ['elementType' => 'geometry', 'stylers' => [['color' => '#242f3e']]], |
| | | ['elementType' => 'labels.text.fill', 'stylers' => [['color' => '#746855']]], |
| | | ['elementType' => 'labels.text.stroke', 'stylers' => [['color' => '#242f3e']]], |
| | | ['featureType' => 'road', 'elementType' => 'geometry', 'stylers' => [['color' => '#38414e']]], |
| | | ['featureType' => 'water', 'elementType' => 'geometry', 'stylers' => [['color' => '#17263c']]] |
| | | ]; |
| | | |
| | | case 'minimal': |
| | | return [ |
| | | ['featureType' => 'poi', 'stylers' => [['visibility' => 'off']]], |
| | | ['featureType' => 'transit', 'stylers' => [['visibility' => 'off']]] |
| | | ]; |
| | | |
| | | default: |
| | | return []; |
| | | } |
| | | return match ($theme) { |
| | | 'dark' => [ |
| | | ['elementType' => 'geometry', 'stylers' => [['color' => '#242f3e']]], |
| | | ['elementType' => 'labels.text.fill', 'stylers' => [['color' => '#746855']]], |
| | | ['elementType' => 'labels.text.stroke', 'stylers' => [['color' => '#242f3e']]], |
| | | ['featureType' => 'road', 'elementType' => 'geometry', 'stylers' => [['color' => '#38414e']]], |
| | | ['featureType' => 'water', 'elementType' => 'geometry', 'stylers' => [['color' => '#17263c']]] |
| | | ], |
| | | 'minimal' => [ |
| | | ['featureType' => 'poi', 'stylers' => [['visibility' => 'off']]], |
| | | ['featureType' => 'transit', 'stylers' => [['visibility' => 'off']]] |
| | | ], |
| | | default => [], |
| | | }; |
| | | } |
| | | |
| | | /** |
| | |
| | | use JVBase\integrations\OAuthCredentials; |
| | | use JVBase\integrations\OAuthURLs; |
| | | use JVBase\integrations\Requests; |
| | | use JVBase\integrations\SyncTo; |
| | | use JVBase\integrations\SyncToPost; |
| | | use JVBase\integrations\UserConnection; |
| | | use WP_Error; |
| | |
| | | ]; |
| | | } |
| | | |
| | | protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array |
| | | protected function handleResponse(WP_Error|array $response, string $method, string $endpoint): array |
| | | { |
| | | if (is_wp_error($response) && $response->get_error_message() === 'rate_limit_exceeded') { |
| | | return $this->response(false, 'Rate limit exceeded'); |
| | |
| | | <?php |
| | | namespace JVBase\integrations\services; |
| | | use Exception; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\meta\Meta; |
| | | use WP_Error; |
| | | use WP_Post; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | try { |
| | | $accounts = $this->getAccounts(true); |
| | | return !empty($accounts); |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | return false; |
| | | } |
| | | } |
| | |
| | | { |
| | | // Google APIs return errors in this format: |
| | | // {"error": {"code": 401, "message": "...", "status": "UNAUTHENTICATED"}} |
| | | return isset($response['error']) && isset($response['error']['code']); |
| | | return isset($response['error']['code']); |
| | | } |
| | | |
| | | protected function getRequestHeaders(): array |
| | |
| | | return $credentials; |
| | | } |
| | | |
| | | public function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings):void |
| | | public function handleTheSavePost(int $postID, WP_Post $post, bool $update, array $settings):void |
| | | { |
| | | |
| | | if (!$this->hasOAuthCredentials()) { |
| | |
| | | try { |
| | | $gmb = (array_key_exists('user', $data) && $data['user'] !== 0) ? $this->getInstance((int)$data['user']) : $this; |
| | | $gmb->ensureInitialized(); |
| | | switch ($operation->type) { |
| | | case 'gmb_create_menu_item': |
| | | case 'gmb_update_menu_item': |
| | | case 'gmb_menu_update': |
| | | case 'gmb_menu_update_' . $this->userID: |
| | | return $gmb->processMenuUpdate($operation, $data); |
| | | case 'gmb_create_event': |
| | | case 'gmb_update_event': |
| | | return $gmb->syncEventPost($operation, $data); |
| | | case 'gmb_create_offer': |
| | | case 'gmb_update_offer': |
| | | return $gmb->syncOfferPost($operation, $data); |
| | | case 'gmb_create_post': |
| | | case 'gmb_update_post': |
| | | return $gmb->syncStandardPost($operation, $data); |
| | | case 'gmb_specialHours': |
| | | return $gmb->setSpecialHours($data['date'], $data['data']); |
| | | case 'gmb_business_hours': |
| | | return $gmb->updateBusinessHours($data); |
| | | case 'gmb_business_info': |
| | | return $gmb->updateBusinessInfo($data); |
| | | case 'gmb_update_attributes': |
| | | return $gmb->updateAttributes($data); |
| | | default: |
| | | return $result; |
| | | } |
| | | } catch (\Exception $e) { |
| | | return match ($operation->type) { |
| | | 'gmb_create_menu_item', 'gmb_update_menu_item', 'gmb_menu_update', 'gmb_menu_update_' . $this->userID => $gmb->processMenuUpdate($operation, $data), |
| | | 'gmb_create_event', 'gmb_update_event' => $gmb->syncEventPost($operation, $data), |
| | | 'gmb_create_offer', 'gmb_update_offer' => $gmb->syncOfferPost($operation, $data), |
| | | 'gmb_create_post', 'gmb_update_post' => $gmb->syncStandardPost($operation, $data), |
| | | 'gmb_specialHours' => $gmb->setSpecialHours($data['date'], $data['data']), |
| | | 'gmb_business_hours' => $gmb->updateBusinessHours($data), |
| | | 'gmb_business_info' => $gmb->updateBusinessInfo($data), |
| | | 'gmb_update_attributes' => $gmb->updateAttributes($data), |
| | | default => $result, |
| | | }; |
| | | } catch (Exception $e) { |
| | | error_log('[GMB] Operation processing error: ' . $e->getMessage()); |
| | | return [ |
| | | 'success' => false, |
| | |
| | | 'result' => (is_array($result)) ? array_merge($result, ['menu'=>$menu_data]) : ['menu'=>$menu_data], |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | error_log('GMB Menu Update Error: ' . $e->getMessage()); |
| | | return [ |
| | | 'success' => false, |
| | |
| | | } |
| | | } |
| | | } |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | error_log('[GMB] Error finding location resource name: ' . $e->getMessage()); |
| | | } |
| | | |
| | |
| | | |
| | | $location_name = $this->getSelectedLocationResourceName(); |
| | | if (empty($patch_data)) { |
| | | throw new \Exception('No valid fields to update'); |
| | | throw new Exception('No valid fields to update'); |
| | | } |
| | | |
| | | // Create updateMask from the fields being updated |
| | |
| | | 'result' => $response |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'updateBusinessInfo' |
| | | ]); |
| | |
| | | 'result' => $response |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'updateAttributes' |
| | | ]); |
| | |
| | | try { |
| | | // Validate required fields |
| | | if (empty($post_data['summary'])) { |
| | | throw new \Exception('Post summary is required'); |
| | | throw new Exception('Post summary is required'); |
| | | } |
| | | |
| | | // Build the post object according to API docs |
| | |
| | | |
| | | return $response; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'createPost' |
| | | ]); |
| | |
| | | |
| | | return !empty($response); |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'updatePost' |
| | |
| | | try { |
| | | $this->makeRequest('DELETE', "/v1/accounts/{$post_name}", [], 'posts'); |
| | | return true; |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'deletePost' |
| | |
| | | |
| | | if (empty($location_name)) { |
| | | error_log('[GMB] ERROR: No location selected or found'); |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | try { |
| | |
| | | |
| | | if (empty($periods)) { |
| | | error_log('[GMB] ERROR: No valid periods found after validation'); |
| | | throw new \Exception('No valid business hours provided'); |
| | | throw new Exception('No valid business hours provided'); |
| | | } |
| | | |
| | | $update_data = [ |
| | |
| | | 'result' => $response |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'updateBusinessHours' |
| | | ]); |
| | |
| | | $location_name = $this->getSelectedLocationResourceName(); |
| | | |
| | | if (empty($location_name)) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | try { |
| | |
| | | } |
| | | } |
| | | |
| | | if (!empty($options['close'])) { |
| | | $close_time = $this->formatTime($options['close']); |
| | | if (!empty($options['closed'])) { |
| | | $close_time = $this->formatTime($options['closed']); |
| | | if ($close_time) { |
| | | $special_hour_period['closeTime'] = $close_time; |
| | | } |
| | |
| | | 'results' => $response |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'setSpecialHours' |
| | | ]); |
| | |
| | | |
| | | return $response !== null; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'clearSpecialHours' |
| | |
| | | ]; |
| | | |
| | | if (!in_array($category, $valid_categories)) { |
| | | throw new \Exception('Invalid photo category: ' . $category); |
| | | throw new Exception('Invalid photo category: ' . $category); |
| | | } |
| | | $mediaKey = $this->getImageMediaKey($imgID); |
| | | if ($mediaKey) { |
| | |
| | | return $mediaKey; |
| | | } |
| | | return null; |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'uploadPhotoFromUrl' |
| | | ]); |
| | |
| | | { |
| | | $this->ensureInitialized(); |
| | | if (!$this->location) { |
| | | throw new \Exception('No location selected'); |
| | | throw new Exception('No location selected'); |
| | | } |
| | | if (!$this->account_id) { |
| | | throw new \Exception('No account configured'); |
| | | throw new Exception('No account configured'); |
| | | } |
| | | |
| | | $location = $this->getSelectedLocationResourceName(); |
| | |
| | | |
| | | return $reviews; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'getReviews' |
| | | ]); |
| | |
| | | |
| | | return null; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to get reviews view URL: ' . $e->getMessage(), [ |
| | | 'method' => 'getReviewsViewUrl' |
| | | ]); |
| | |
| | | |
| | | return null; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to get review URL: ' . $e->getMessage(), [ |
| | | 'method' => 'getReviewUrl' |
| | | ]); |
| | |
| | | |
| | | try { |
| | | $response = $this->getRequest("/v1/{$account_name}/locations", $params,'accounts', $ttl, $force); |
| | | return isset($response['locations']) ? $response['locations'] : []; |
| | | return $response['locations'] ?? []; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | error_log('[GMB] Error getting locations for ' . $account_name . ': ' . $e->getMessage()); |
| | | return []; |
| | | } |
| | |
| | | 'message' => 'No accounts found' |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | error_log('[GMB] Error refreshing stored data: ' . $e->getMessage()); |
| | | return [ |
| | | 'success' => false, |
| | |
| | | ?> |
| | | </div> |
| | | <?php |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | echo '<p>Error loading business locations.</p>'; |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | if (!$selected_account) { |
| | | throw new \Exception('Invalid account selected'); |
| | | throw new Exception('Invalid account selected'); |
| | | } |
| | | |
| | | // Update credentials with selected account |
| | |
| | | 'reload' => true |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to select account', [ |
| | | 'error' => $e->getMessage(), |
| | | 'data' => $data |
| | |
| | | } |
| | | |
| | | if (empty($location_name)) { |
| | | throw new \Exception('No location selected'); |
| | | throw new Exception('No location selected'); |
| | | } |
| | | |
| | | // Verify this is a valid location for the selected account |
| | | if (empty($this->credentials['account'])) { |
| | | throw new \Exception('No account selected. Please select an account first.'); |
| | | throw new Exception('No account selected. Please select an account first.'); |
| | | } |
| | | |
| | | $locations = $this->getLocations($this->credentials['account']); |
| | |
| | | } |
| | | |
| | | if (!$valid_location) { |
| | | throw new \Exception('Invalid location selected'); |
| | | throw new Exception('Invalid location selected'); |
| | | } |
| | | |
| | | // Update credentials with selected location |
| | |
| | | 'reload' => false // No need to reload for location selection |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to select location', [ |
| | | 'error' => $e->getMessage(), |
| | | 'data' => $data |
| | |
| | | 'has_refresh_token' => !empty($this->credentials['refresh_token']) |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | return [ |
| | | 'success' => false, |
| | | 'valid' => false, |
| | |
| | | public function getFoodMenus(): ?array |
| | | { |
| | | if (!$this->credentials['location']) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | $response = $this->getRequest( |
| | |
| | | public function updateFoodMenus(array $menus): bool |
| | | { |
| | | if (!$this->credentials['location']) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | try { |
| | |
| | | ]); |
| | | return !empty($response); |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'updateFoodMenus' |
| | | ]); |
| | |
| | | { |
| | | // Extract mediaKey from format: accounts/{accountId}/locations/{locationId}/media/{mediaKey} |
| | | $parts = explode('/media/', $mediaResourceName); |
| | | return isset($parts[1]) ? $parts[1] : ''; |
| | | return $parts[1] ?? ''; |
| | | } |
| | | |
| | | /** |
| | |
| | | public function getLocationMedia(): array |
| | | { |
| | | if (!$this->credentials['location']) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | try { |
| | |
| | | ); |
| | | |
| | | return $response['mediaItems'] ?? []; |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to get location media: ' . $e->getMessage()); |
| | | return []; |
| | | } |
| | |
| | | 'cached' => !$force |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | return new WP_Error('failure', 'Something went wrong: '.$e->getMessage()); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | if (empty($selected_account)) { |
| | | throw new \Exception('No account selected'); |
| | | throw new Exception('No account selected'); |
| | | } |
| | | |
| | | // Save the selected account |
| | |
| | | 'reload' => true // Trigger page refresh to show location dropdown |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to update location', [ |
| | | 'error' => $e->getMessage(), |
| | | 'data' => $data |
| | |
| | | $this->cache->flush(); |
| | | return true; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | error_log('[GMB] Error clearing stored data: ' . $e->getMessage()); |
| | | return false; |
| | | } |
| | |
| | | public function getPriceLists(): ?array |
| | | { |
| | | if (!$this->credentials['location']) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | try { |
| | | $location_data = $this->getLocation($this->credentials['location']); |
| | | return $location_data['priceLists'] ?? []; |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'getPriceLists' |
| | |
| | | $location_name = $this->getSelectedLocationResourceName(); |
| | | |
| | | if (empty($location_name)) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | $post_data = [ |
| | |
| | | $location_name = $this->getSelectedLocationResourceName(); |
| | | |
| | | if (empty($location_name)) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | return $this->updateBusinessInfo($location_name, $updates); |
| | |
| | | $location_name = $this->getSelectedLocationResourceName(); |
| | | |
| | | if (empty($location_name)) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | return $this->clearSpecialHours($location_name); |
| | |
| | | return [$gmb_menu]; // GMB expects an array of menus |
| | | } |
| | | |
| | | protected function buildMenuItem(\WP_Post $item, array $defaults):array |
| | | protected function buildMenuItem(WP_Post $item, array $defaults):array |
| | | { |
| | | $meta = Meta::forPost($item->ID); |
| | | $fields = $this->mappedMenuFields($item->post_type); |
| | |
| | | use JVBase\integrations\Admin; |
| | | use JVBase\Instance; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\managers\queue\executors\IntegrationExecutor; |
| | | use JVBase\managers\queue\TypeConfig; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\ui\Checkout; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | |
| | | namespace JVBase\integrations\services; |
| | | use Exception; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\managers\queue\executors\IntegrationExecutor; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\managers\queue\TypeConfig; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\ui\Checkout; |
| | | use WP_Error; |
| | | use WP_Post; |
| | | use WP_REST_Request; |
| | | use WP_REST_Response; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | * PRODUCT SYNC |
| | | *****************************************************************/ |
| | | |
| | | protected function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings): void |
| | | protected function handleTheSavePost(int $postID, WP_Post $post, bool $update, array $settings): void |
| | | { |
| | | |
| | | error_log('==== [Helcim]::handleTheSavePost ===='); |
| | |
| | | /** |
| | | * Override the webhook request handler to capture raw body for signature verification |
| | | */ |
| | | public function handleWebhookRequest(\WP_REST_Request $request): \WP_REST_Response |
| | | public function handleWebhookRequest(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $payload = $request->get_params(); |
| | | $payload['_headers'] = $request->get_headers(); |
| | |
| | | |
| | | $success = $this->handleWebhook($payload); |
| | | |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => $success, |
| | | ], $success ? 200 : 400); |
| | | } |
| | |
| | | * Sync a WordPress post to Helcim as a product. |
| | | * Called by IntegrationExecutor::processSyncTo() |
| | | */ |
| | | public function syncPostToService(int $postID): array|\WP_Error |
| | | public function syncPostToService(int $postID): array|WP_Error |
| | | { |
| | | $post = get_post($postID); |
| | | if (!$post) { |
| | | return new \WP_Error('not_found', "Post {$postID} not found"); |
| | | return new WP_Error('not_found', "Post {$postID} not found"); |
| | | } |
| | | |
| | | $helcimProductId = get_post_meta($postID, BASE . '_helcim_item_id', true); |
| | |
| | | * Delete a product from Helcim. |
| | | * Called by IntegrationExecutor::processDeleteFrom() |
| | | */ |
| | | public function deleteFromService(string $externalId): array|\WP_Error |
| | | public function deleteFromService(string $externalId): array|WP_Error |
| | | { |
| | | $response = $this->deleteRequest("products/{$externalId}"); |
| | | |
| | |
| | | /** |
| | | * Build Helcim product payload from a WordPress post. |
| | | */ |
| | | protected function buildProductPayload(int $postID): array|\WP_Error |
| | | protected function buildProductPayload(int $postID): array|WP_Error |
| | | { |
| | | $meta = \JVBase\meta\Meta::forPost($postID); |
| | | $meta = Meta::forPost($postID); |
| | | $post = get_post($postID); |
| | | |
| | | $price = $meta->get('price'); |
| | | if (empty($price) || !is_numeric($price)) { |
| | | return new \WP_Error('invalid_price', "Post {$postID} has no valid price"); |
| | | return new WP_Error('invalid_price', "Post {$postID} has no valid price"); |
| | | } |
| | | |
| | | return [ |
| | |
| | | use JVBase\integrations\OAuthCredentials; |
| | | use JVBase\integrations\OAuthURLs; |
| | | use JVBase\integrations\Requests; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | return []; |
| | | } |
| | | |
| | | protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array |
| | | protected function handleResponse(WP_Error|array $response, string $method, string $endpoint): array |
| | | { |
| | | // TODO: Implement handleResponse() method. |
| | | return []; |
| | |
| | | */ |
| | | |
| | | namespace JVBase\integrations\services; |
| | | use Exception; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\meta\Meta; |
| | | use WP_Error; |
| | | use WP_Post; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | /** |
| | | * Handle post save - queue Instagram post creation |
| | | */ |
| | | public function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings): void |
| | | public function handleTheSavePost(int $postID, WP_Post $post, bool $update, array $settings): void |
| | | { |
| | | |
| | | if (!$this->hasOAuthCredentials()) { |
| | |
| | | /** |
| | | * Process queued operations |
| | | */ |
| | | public function processOperation(\WP_Error|array $result, object $operation, array $data): \WP_Error|array |
| | | public function processOperation(WP_Error|array $result, object $operation, array $data): WP_Error|array |
| | | { |
| | | $base = strtolower($this->service_name) . '_'; |
| | | $insta = (array_key_exists('user', $data)) ? new self((int)$data['user']) : $this; |
| | | switch ($operation->type) { |
| | | case $base . 'create_post': |
| | | return $insta->processCreatePost($data); |
| | | |
| | | case $base . 'create_story': |
| | | return $insta->processCreateStory($data); |
| | | |
| | | case $base . 'delete_post': |
| | | return $insta->processDeletePost($data); |
| | | |
| | | default: |
| | | return $result; |
| | | } |
| | | return match ($operation->type) { |
| | | $base . 'create_post' => $insta->processCreatePost($data), |
| | | $base . 'create_story' => $insta->processCreateStory($data), |
| | | $base . 'delete_post' => $insta->processDeletePost($data), |
| | | default => $result, |
| | | }; |
| | | } |
| | | |
| | | private function processDeletePost(array $data): array |
| | |
| | | /** |
| | | * Build caption for Instagram post |
| | | */ |
| | | private function buildCaption(\WP_Post $post): string |
| | | private function buildCaption(WP_Post $post): string |
| | | { |
| | | $caption_parts = []; |
| | | |
| | |
| | | $default = preg_split('/[\s,]+/', $this->post_settings['default_hashtags']); |
| | | foreach ($default as $tag) { |
| | | $tag = trim($tag); |
| | | if (strpos($tag, '#') !== 0) { |
| | | if (!str_starts_with($tag, '#')) { |
| | | $tag = '#' . $tag; |
| | | } |
| | | $hashtags[] = $tag; |
| | |
| | | ]; |
| | | } |
| | | |
| | | protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array |
| | | protected function handleResponse(WP_Error|array $response, string $method, string $endpoint): array |
| | | { |
| | | if (is_wp_error($response) && $response->get_error_message() === 'rate_limit_exceeded') { |
| | | return $this->response(false, 'Rate limit exceeded'); |
| | |
| | | <?php |
| | | |
| | | namespace JVBase\integrations\services; |
| | | use Exception; |
| | | use JVBase\integrations\Integrations; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | |
| | | // Determine content type |
| | | $is_html = $parsed_headers['content-type'] === 'text/html' || |
| | | strpos($message, '<html') !== false || |
| | | strpos($message, '<body') !== false; |
| | | str_contains($message, '<html') || |
| | | str_contains($message, '<body'); |
| | | |
| | | // Build PostMark payload |
| | | $payload = $this->buildEmailPayload( |
| | |
| | | |
| | | return true; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('[POSTMARK]', ['method' => 'sendEmail', 'error' => $e->getMessage()]); |
| | | return new WP_Error( |
| | | 'postmark_send_failed', |
| | |
| | | |
| | | switch ($name) { |
| | | case 'content-type': |
| | | if (strpos($value, 'text/html') !== false) { |
| | | if (str_contains($value, 'text/html')) { |
| | | $parsed['content-type'] = 'text/html'; |
| | | } |
| | | break; |
| | |
| | | |
| | | default: |
| | | // Store custom headers |
| | | if (strpos($name, 'x-') === 0) { |
| | | if (str_starts_with($name, 'x-')) { |
| | | $parsed['x-headers'][] = [ |
| | | 'Name' => $parts[0], // Original case |
| | | 'Value' => $value |
| | |
| | | $this->cache->set($cache_key, $stats, 3600); // Cache for 1 hour |
| | | return $stats; |
| | | } |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('stats/outbound', ['error' => $e->getMessage()]); |
| | | } |
| | | |
| | |
| | | 'count' => $count, |
| | | 'offset' => $offset |
| | | ]); |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('[POSTMARK]', ['method' => 'getBounces', 'error' => $e->getMessage()]); |
| | | return []; |
| | | } |
| | |
| | | use JVBase\integrations\SyncFrom; |
| | | use JVBase\integrations\SyncTo; |
| | | use JVBase\integrations\Webhooks; |
| | | use JVBase\managers\Dashboard\Dashboard; |
| | | use JVBase\managers\queue\executors\IntegrationExecutor; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\managers\queue\TypeConfig; |
| | | use JVBase\meta\Sanitizer; |
| | | use JVBase\registrar\Registrar; |
| | |
| | | ]; |
| | | } |
| | | |
| | | protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array |
| | | protected function handleResponse(WP_Error|array $response, string $method, string $endpoint): array |
| | | { |
| | | if (is_wp_error($response) && $response->get_error_message() === 'rate_limit_exceeded') { |
| | | return $this->response(false, 'Rate limit exceeded'); |
| | |
| | | namespace JVBase\integrations\services; |
| | | use Exception; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\managers\Emails; |
| | | use JVBase\managers\queue\executors\IntegrationExecutor; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\managers\queue\TypeConfig; |
| | | use JVBase\meta\Form; |
| | | use JVBase\meta\Meta; |
| | |
| | | |
| | | protected string $environment = 'sandbox'; |
| | | |
| | | private const PASSWORD_RESET_INTERVAL = 3; // Reset password every 3 logins, for customers |
| | | // Reset password every 3 logins, for customers |
| | | |
| | | /** |
| | | * OAuth Configuration |
| | |
| | | use JVBase\integrations\Admin; |
| | | use JVBase\Instance; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\managers\Roles; |
| | | use JVBase\registrar\Registrar; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | <?php |
| | | namespace JVBase\integrations\services; |
| | | use Exception; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | $referer = $_SERVER['HTTP_REFERER']; |
| | | $site_url = get_site_url(); |
| | | |
| | | if (strpos($referer, $site_url) === false) { |
| | | if (!str_contains($referer, $site_url)) { |
| | | $domain = parse_url($referer, PHP_URL_HOST); |
| | | return $domain ?: 'external'; |
| | | } |
| | |
| | | /** |
| | | * Process queued operations |
| | | */ |
| | | public function processOperation(array|\WP_Error $result, object $operation, array $data): array|\WP_Error |
| | | public function processOperation(array|WP_Error $result, object $operation, array $data): array|WP_Error |
| | | { |
| | | $prefix = strtolower($this->service_name) . '_'; |
| | | |
| | |
| | | namespace JVBase\managers; |
| | | |
| | | use JVBase\Instance; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\base\Site; |
| | | use WP_REST_Response; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | public function enqueueAdminAssets(string $hook):void |
| | | { |
| | | // More robust check for JVB admin pages |
| | | if (strpos($hook, BASE) === false) { |
| | | if (!str_contains($hook, BASE)) { |
| | | return; |
| | | } |
| | | |
| | |
| | | $group = jvbNoBase($group); |
| | | |
| | | $registered = Registrar::getRegistered(); |
| | | foreach ($registered as $r) { |
| | | if ($r === $group) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | return in_array($group, $registered, true); |
| | | } |
| | | |
| | | protected function formatConnections(array $data): string |
| | |
| | | <?php if (empty($versions)): ?> |
| | | <tr><td colspan="5">No version history available</td></tr> |
| | | <?php else: ?> |
| | | <?php foreach (array_reverse($versions) as $index => $version): ?> |
| | | <?php foreach (array_reverse($versions) as $version): ?> |
| | | <tr> |
| | | <th class="check-column"> |
| | | <input type="checkbox" |
| | |
| | | public function handleAdminPageSubmission(): void |
| | | { |
| | | // Only process on our admin pages |
| | | if (!isset($_GET['page']) || strpos($_GET['page'], BASE) !== 0) { |
| | | if (!isset($_GET['page']) || !str_starts_with($_GET['page'], BASE)) { |
| | | return; |
| | | } |
| | | |
| | |
| | | $types = Registrar::withFeature('approve_new'); |
| | | foreach ($types as $type) { |
| | | $requests = CustomTable::for("approval_{$type}_requests"); |
| | | $registrar = Registrar::getInstance($type); |
| | | // $registrar = Registrar::getInstance($type); |
| | | |
| | | $requests->setColumns([ |
| | | 'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT', |
| | |
| | | } |
| | | } elseif ($registrar->getType() === 'user') { |
| | | $user = get_userdata($request['user_id']); |
| | | $user->add_cap('skip_moderation', true); |
| | | $user->add_cap('skip_moderation'); |
| | | update_user_meta($request['user_id'], BASE.'verification_date', current_time('mysql')); |
| | | } |
| | | |
| | |
| | | }, $allRequests); |
| | | } else { |
| | | foreach ($this->tables as $type => $tables) { |
| | | $requests = $tables['requests'];; |
| | | $requests = $tables['requests']; |
| | | $active = $requests->pluck('request_id', [ |
| | | 'status' => 'pending', |
| | | ]); |
| | |
| | | $value = $callback(); |
| | | |
| | | if ($value !== null && $value !== false) { |
| | | $this->set($key, $value); |
| | | $this->set($key, $value, $ttl); |
| | | } |
| | | |
| | | return $value; |
| | |
| | | <?php |
| | | namespace JVBase\managers; |
| | | |
| | | use Redis; |
| | | use WP_Object_Cache; |
| | | use WP_Post; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | return $value; |
| | | } |
| | | |
| | | public function get(int|string|array $id): mixed |
| | | public function get(int|string|array $id): bool |
| | | { |
| | | return false; |
| | | if (is_array($id)) { |
| | |
| | | /**************************************************** |
| | | * REDIS |
| | | ****************************************************/ |
| | | private static function redis(): ?\Redis |
| | | private static function redis(): ?Redis |
| | | { |
| | | global $wp_object_cache; |
| | | |
| | | return $wp_object_cache instanceof \WP_Object_Cache |
| | | return $wp_object_cache instanceof WP_Object_Cache |
| | | && isset($wp_object_cache->redis) |
| | | ? $wp_object_cache->redis |
| | | : null; |
| | |
| | | /**************************************************** |
| | | * HOOKS - Posts |
| | | ****************************************************/ |
| | | public static function onPostChange(int $postId, \WP_Post $post): void |
| | | public static function onPostChange(int $postId, WP_Post $post): void |
| | | { |
| | | if (wp_is_post_revision($postId) || wp_is_post_autosave($postId)) { |
| | | return; |
| | |
| | | namespace JVBase\managers; |
| | | |
| | | use Exception; |
| | | use wpdb; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | */ |
| | | class CustomTable |
| | | { |
| | | protected \wpdb $wpdb; |
| | | |
| | | private static ?wpdb $wpdb = null; |
| | | private static string $prefix; |
| | | protected string $tableName; |
| | | protected string $definition; |
| | | protected string $fullTableName; |
| | |
| | | protected function ensureUserTable():void |
| | | { |
| | | if (!isset(static::$userTable)) { |
| | | static::$userTable = is_multisite() ? $this->getMultisiteUsersTable() : $this->wpdb->users; |
| | | static::$userTable = is_multisite() ? $this->getMultisiteUsersTable() : self::$wpdb->users; |
| | | } |
| | | } |
| | | protected function getMultisiteUsersTable():string |
| | | { |
| | | $siteUsersTable = $this->wpdb->prefix . 'users'; |
| | | $siteExists = $this->wpdb->get_var( |
| | | $this->wpdb->prepare("SHOW TABLES LIKE %s", $siteUsersTable) |
| | | $siteUsersTable = self::$wpdb->prefix . 'users'; |
| | | $siteExists = self::$wpdb->get_var( |
| | | self::$wpdb->prepare("SHOW TABLES LIKE %s", $siteUsersTable) |
| | | ); |
| | | if ($siteExists) { |
| | | return $siteUsersTable; |
| | | } |
| | | //fallback to main one |
| | | return $this->wpdb->users; |
| | | return self::$wpdb->users; |
| | | } |
| | | protected function ensureUserIDType():void |
| | | { |
| | |
| | | protected function ensureTermIDType():void |
| | | { |
| | | if (!isset(static::$termIDType)) { |
| | | static::$termIDType = $this->getColumnType($this->wpdb->terms, 'term_id'); |
| | | static::$termIDType = $this->getColumnType(self::$wpdb->terms, 'term_id'); |
| | | } |
| | | } |
| | | |
| | | protected function ensureTermTable():void |
| | | { |
| | | if (!isset(static::$termTable)) { |
| | | static::$termTable = $this->wpdb->terms; |
| | | static::$termTable = self::$wpdb->terms; |
| | | } |
| | | } |
| | | |
| | | protected function ensurePostIDType():void |
| | | { |
| | | if (!isset(static::$postIDType)) { |
| | | static::$postIDType = $this->getColumnType($this->wpdb->posts, 'ID'); |
| | | static::$postIDType = $this->getColumnType(self::$wpdb->posts, 'ID'); |
| | | } |
| | | } |
| | | protected function ensurePostTable():void |
| | | { |
| | | if (!isset(static::$postTable)) { |
| | | static::$postTable = $this->wpdb->posts; |
| | | static::$postTable = self::$wpdb->posts; |
| | | } |
| | | } |
| | | |
| | |
| | | error_log('[CustomTable]No definition set for '.$this->tableName); |
| | | return; |
| | | } |
| | | if ($this->wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")) { |
| | | if (self::$wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")) { |
| | | return; |
| | | } |
| | | require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); |
| | | error_log('Creating Database Table: '. $this->tableName); |
| | | $charset = self::$charsetCollate; |
| | | $schema = "CREATE TABLE IF NOT EXISTS {$this->fullTableName} {$this->definition} {$charset}"; |
| | | $this->wpdb->flush(); |
| | | self::$wpdb->flush(); |
| | | |
| | | $hasForeignKey = stripos($schema, 'FOREIGN KEY') !== false; |
| | | if ($hasForeignKey) { |
| | | $result = $this->wpdb->query($schema); |
| | | $success = ($result !== false && !$this->wpdb->last_error); |
| | | $result = self::$wpdb->query($schema); |
| | | $success = ($result !== false && !self::$wpdb->last_error); |
| | | } else { |
| | | dbDelta($schema.';'); |
| | | $success = !$this->wpdb->last_error; |
| | | $success = !self::$wpdb->last_error; |
| | | } |
| | | |
| | | if (!$success) { |
| | | $error_msg = "SQL Error creating table {$this->tableName}: " . $this->wpdb->last_error; |
| | | $error_msg = "SQL Error creating table {$this->tableName}: " . self::$wpdb->last_error; |
| | | error_log($error_msg); |
| | | error_log("Failed SQL Query: " . $schema); |
| | | } elseif ($this->wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")) { |
| | | } elseif (self::$wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")) { |
| | | error_log("Successfully created table: {$this->fullTableName}"); |
| | | } |
| | | } |
| | |
| | | self::$instances = []; |
| | | } |
| | | |
| | | protected function checkWPDB():void |
| | | { |
| | | if (is_null(self::$wpdb)) { |
| | | global $wpdb; |
| | | self::$wpdb = $wpdb; |
| | | self::$prefix = $wpdb->prefix; |
| | | |
| | | $usersStatus = self::$wpdb->get_row("SHOW TABLE STATUS LIKE '{self::$wpdb->users}'"); |
| | | $parentCollation = $usersStatus->Collation ?? 'utf8mb4_general_ci'; |
| | | self::$charsetCollate = "DEFAULT CHARACTER SET utf8mb4 COLLATE {$parentCollation}"; |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * @param string $tableName Table name without prefix/BASE (e.g., 'favourites', 'notifications') |
| | | * @param bool $useTransactions Whether to auto-wrap operations in transactions |
| | | */ |
| | | public function __construct(string $tableName, bool $user = false, bool $auth = false, bool $useTransactions = false) |
| | | private function __construct(string $tableName, bool $user = false, bool $auth = false, bool $useTransactions = false) |
| | | { |
| | | global $wpdb; |
| | | $this->wpdb = $wpdb; |
| | | $this->checkWPDB(); |
| | | |
| | | $this->tableName = $tableName; |
| | | $this->fullTableName = $wpdb->prefix . apply_filters('jvb_base', BASE) . $tableName; |
| | | $this->fullTableName = self::$prefix . apply_filters('jvb_base', BASE) . $tableName; |
| | | $this->useTransactions = $useTransactions; |
| | | |
| | | $this->cache = Cache::for($tableName); |
| | |
| | | $this->cache->auth(); |
| | | } |
| | | |
| | | $usersStatus = $this->wpdb->get_row("SHOW TABLE STATUS LIKE '{$this->wpdb->users}'"); |
| | | $parentCollation = $usersStatus->Collation ?? 'utf8mb4_general_ci'; |
| | | self::$charsetCollate = "DEFAULT CHARACTER SET utf8mb4 COLLATE {$parentCollation}"; |
| | | |
| | | } |
| | | |
| | |
| | | $data['created_at'] = current_time('mysql'); |
| | | } |
| | | |
| | | $result = $this->wpdb->insert( |
| | | $result = self::$wpdb->insert( |
| | | $this->fullTableName, |
| | | $data, |
| | | $format |
| | |
| | | |
| | | $this->cache->flush(); |
| | | |
| | | return $this->wpdb->insert_id; |
| | | return self::$wpdb->insert_id; |
| | | } |
| | | |
| | | /** |
| | |
| | | (" . implode(',', $columns_escaped) . ") |
| | | VALUES " . implode(',', $placeholders); |
| | | |
| | | $result = $this->wpdb->query($this->wpdb->prepare($query, $values)); |
| | | $result = self::$wpdb->query(self::$wpdb->prepare($query, $values)); |
| | | |
| | | if ($result === false) { |
| | | $this->logError('bulkInsert', ['rows' => count($rows)]); |
| | |
| | | $query .= " WHERE {$where['sql']}"; |
| | | $values = $where['values']; |
| | | |
| | | return $this->wpdb->get_row($this->wpdb->prepare($query, $values), $output); |
| | | return self::$wpdb->get_row(self::$wpdb->prepare($query, $values), $output); |
| | | } |
| | | ); |
| | | } |
| | |
| | | } |
| | | |
| | | if (empty($values)) { |
| | | return $this->wpdb->get_results($query, $output); |
| | | return self::$wpdb->get_results($query, $output); |
| | | } |
| | | |
| | | return $this->wpdb->get_results($this->wpdb->prepare($query, $values), $output); |
| | | return self::$wpdb->get_results(self::$wpdb->prepare($query, $values), $output); |
| | | } |
| | | ); |
| | | |
| | |
| | | } |
| | | |
| | | if (empty($values)) { |
| | | return (int) $this->wpdb->get_var($query); |
| | | return (int) self::$wpdb->get_var($query); |
| | | } |
| | | |
| | | return (int) $this->wpdb->get_var($this->wpdb->prepare($query, $values)); |
| | | return (int) self::$wpdb->get_var(self::$wpdb->prepare($query, $values)); |
| | | } |
| | | ); |
| | | |
| | |
| | | $data['updated_at'] = current_time('mysql'); |
| | | } |
| | | |
| | | $result = $this->wpdb->update( |
| | | $result = self::$wpdb->update( |
| | | $this->fullTableName, |
| | | $data, |
| | | $where, |
| | |
| | | */ |
| | | public function delete(array $where, ?array $where_format = null): int|false |
| | | { |
| | | $result = $this->wpdb->delete( |
| | | $result = self::$wpdb->delete( |
| | | $this->fullTableName, |
| | | $where, |
| | | $where_format |
| | |
| | | * |
| | | * @param string $query SQL query (use {table} as placeholder) |
| | | * @param array $values Values for prepare() |
| | | * @return mixed Query result |
| | | * @return int|bool|\mysqli_result|null Query result |
| | | * |
| | | * @example |
| | | * $results = $table->query( |
| | |
| | | * [1, 'pending', 'active'] |
| | | * ); |
| | | */ |
| | | public function query(string $query, array $values = []): mixed |
| | | public function query(string $query, array $values = []): int|bool|null|\mysqli_result |
| | | { |
| | | $query = str_replace('{table}', $this->fullTableName, $query); |
| | | |
| | | if (empty($values)) { |
| | | return $this->wpdb->query($query); |
| | | return self::$wpdb->query($query); |
| | | } |
| | | |
| | | return $this->wpdb->query($this->wpdb->prepare($query, $values)); |
| | | return self::$wpdb->query(self::$wpdb->prepare($query, $values)); |
| | | } |
| | | |
| | | /** |
| | |
| | | $query = str_replace('{table}', $this->fullTableName, $query); |
| | | |
| | | if (empty($values)) { |
| | | return $this->wpdb->get_results($query, $output); |
| | | return self::$wpdb->get_results($query, $output); |
| | | } |
| | | |
| | | return $this->wpdb->get_results($this->wpdb->prepare($query, $values), $output); |
| | | return self::$wpdb->get_results(self::$wpdb->prepare($query, $values), $output); |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @param string $query SQL query (use {table} as placeholder) |
| | | * @param array $values Values for prepare() |
| | | * @return mixed |
| | | * @return string|null |
| | | */ |
| | | public function queryVar(string $query, array $values = []): mixed |
| | | public function queryVar(string $query, array $values = []): ?string |
| | | { |
| | | $query = str_replace('{table}', $this->fullTableName, $query); |
| | | |
| | | if (empty($values)) { |
| | | return $this->wpdb->get_var($query); |
| | | return self::$wpdb->get_var($query); |
| | | } |
| | | |
| | | return $this->wpdb->get_var($this->wpdb->prepare($query, $values)); |
| | | return self::$wpdb->get_var(self::$wpdb->prepare($query, $values)); |
| | | } |
| | | |
| | | // ========================================================================= |
| | |
| | | */ |
| | | public function startTransaction(): void |
| | | { |
| | | $this->wpdb->query('START TRANSACTION'); |
| | | self::$wpdb->query('START TRANSACTION'); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public function commit(): void |
| | | { |
| | | $this->wpdb->query('COMMIT'); |
| | | self::$wpdb->query('COMMIT'); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public function rollback(): void |
| | | { |
| | | $this->wpdb->query('ROLLBACK'); |
| | | self::$wpdb->query('ROLLBACK'); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public function getInsertId(): int |
| | | { |
| | | return $this->wpdb->insert_id; |
| | | return self::$wpdb->insert_id; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public function getLastError(): string |
| | | { |
| | | return $this->wpdb->last_error; |
| | | return self::$wpdb->last_error; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public function getAffectedRows(): int |
| | | { |
| | | return $this->wpdb->rows_affected; |
| | | return self::$wpdb->rows_affected; |
| | | } |
| | | |
| | | public function getUserIDType():string |
| | |
| | | // PRIVATE HELPERS |
| | | // ========================================================================= |
| | | private function getColumnType(string $table, string $column):string|false { |
| | | $tableExists = $this->wpdb->get_var( |
| | | $this->wpdb->prepare("SHOW TABLES LIKE %s", $table) |
| | | $tableExists = self::$wpdb->get_var( |
| | | self::$wpdb->prepare("SHOW TABLES LIKE %s", $table) |
| | | ); |
| | | if (!$tableExists) { |
| | | error_log("[CustomTable] Table {$table} does not exist for getColumnType"); |
| | | return 'bigint(20) unsigned'; // safe fallback |
| | | } |
| | | |
| | | $result = $this->wpdb->get_row( |
| | | $this->wpdb->prepare( |
| | | $result = self::$wpdb->get_row( |
| | | self::$wpdb->prepare( |
| | | "SELECT COLUMN_TYPE |
| | | FROM INFORMATION_SCHEMA.COLUMNS |
| | | WHERE TABLE_SCHEMA = DATABASE() |
| | |
| | | |
| | | private function tableExists():bool |
| | | { |
| | | return !is_null($this->wpdb->get_var($this->wpdb->prepare("SHOW TABLES LIKE %s", $this->fullTableName))); |
| | | return !is_null(self::$wpdb->get_var(self::$wpdb->prepare("SHOW TABLES LIKE %s", $this->fullTableName))); |
| | | } |
| | | /** |
| | | * Build WHERE clause from associative array |
| | |
| | | static $cache = []; |
| | | |
| | | if (!isset($cache[$this->tableName])) { |
| | | $columns = $this->wpdb->get_col("DESCRIBE {$this->fullTableName}"); |
| | | $columns = self::$wpdb->get_col("DESCRIBE {$this->fullTableName}"); |
| | | $cache[$this->tableName] = array_flip($columns); |
| | | } |
| | | |
| | |
| | | if (function_exists('JVB')) { |
| | | Errors::getInstance()->log( |
| | | $this->tableName, |
| | | "CustomTable {$operation} failed: " . $this->wpdb->last_error, |
| | | $context, |
| | | 'error' |
| | | "CustomTable {$operation} failed: " . self::$wpdb->last_error, |
| | | $context |
| | | ); |
| | | } else { |
| | | error_log("[CustomTable:{$this->tableName}] {$operation} failed: " . $this->wpdb->last_error); |
| | | error_log("[CustomTable:{$this->tableName}] {$operation} failed: " . self::$wpdb->last_error); |
| | | } |
| | | } |
| | | |
| | |
| | | namespace JVBase\managers\Dashboard; |
| | | |
| | | use JVBase\managers\Cache; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Query; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | $confirm_url = $email_data['confirm_url']; |
| | | $request_type = $email_data['action_name']; |
| | | |
| | | switch ($request_type) { |
| | | case 'export_personal_data': |
| | | $request_name = 'Export Personal Data'; |
| | | break; |
| | | case 'remove_personal_data': |
| | | $request_name = 'Erase Personal Data'; |
| | | break; |
| | | default: |
| | | $request_name = 'Data Request'; |
| | | } |
| | | $request_name = match ($request_type) { |
| | | 'export_personal_data' => 'Export Personal Data', |
| | | 'remove_personal_data' => 'Erase Personal Data', |
| | | default => 'Data Request', |
| | | }; |
| | | |
| | | |
| | | $message = sprintf( |
| | |
| | | $user_was_logged_in = $user_id > 0 || (!empty($context['isLoggedIn'])); |
| | | |
| | | // Determine source from context |
| | | $source = isset($context['source']) ? $context['source'] : |
| | | (isset($context['url']) ? 'frontend' : 'backend'); |
| | | $source = $context['source'] ?? (isset($context['url']) ? 'frontend' : 'backend'); |
| | | |
| | | $result = $this->table->insert( |
| | | [ |
| | |
| | | ); |
| | | } |
| | | |
| | | $body .= Emails::getInstance()->spacer(20); |
| | | $body .= Emails::getInstance()->spacer(); |
| | | |
| | | // Frequent errors section |
| | | if (!empty($summary['frequent'])) { |
| | |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\rest\routes\FavouritesRoutes; |
| | | use WP_Query; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | |
| | | /** |
| | | * Join favourite counts for WP_Query when orderby => 'favourite_count' |
| | | */ |
| | | public function maybeJoinFavouriteOrderby(string $join, \WP_Query $query): string |
| | | public function maybeJoinFavouriteOrderby(string $join, WP_Query $query): string |
| | | { |
| | | if ($query->get('orderby') !== 'favourites_count') { |
| | | return $join; |
| | |
| | | return $join; |
| | | } |
| | | |
| | | public function maybeApplyFavouriteOrderby(string $orderby, \WP_Query $query): string |
| | | public function maybeApplyFavouriteOrderby(string $orderby, WP_Query $query): string |
| | | { |
| | | if ($query->get('orderby') !== 'favourites_count') { |
| | | return $orderby; |
| | |
| | | <?php |
| | | namespace JVBase\managers; |
| | | |
| | | use Exception; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\base\Site; |
| | | |
| | |
| | | */ |
| | | public function __wakeup() |
| | | { |
| | | throw new \Exception("Cannot unserialize singleton"); |
| | | throw new Exception("Cannot unserialize singleton"); |
| | | } |
| | | |
| | | /** |
| | |
| | | <?php |
| | | namespace JVBase\managers; |
| | | |
| | | use JVBase\managers\UploadManager; |
| | | use WP_Error; |
| | | use Imagick; |
| | | use ImagickPixel; |
| | |
| | | $artist_name = $this->data['display_name']; |
| | | $city = $this->data['city']; |
| | | |
| | | switch ($this->type) { |
| | | case 'shop': |
| | | $title = 'Featured Shop Image'; |
| | | break; |
| | | default: |
| | | $title = 'Featured Artist Image'; |
| | | } |
| | | $title = match ($this->type) { |
| | | 'shop' => 'Featured Shop Image', |
| | | default => 'Featured Artist Image', |
| | | }; |
| | | |
| | | $attachment = [ |
| | | 'post_mime_type' => 'image/webp', |
| | |
| | | require_once(ABSPATH . 'wp-admin/includes/media.php'); |
| | | |
| | | |
| | | switch ($this->type) { |
| | | case 'shop': |
| | | $type = 'Tattoo Studio'; |
| | | break; |
| | | default: |
| | | $type = $this->data['type']; |
| | | break; |
| | | } |
| | | $type = match ($this->type) { |
| | | 'shop' => 'Tattoo Studio', |
| | | default => $this->data['type'], |
| | | }; |
| | | // Set alt text |
| | | $alt_text = sprintf( |
| | | 'Featured image for %s - %sin %s', |
| | |
| | | protected function generateFilename():void |
| | | { |
| | | if ($this->fileManager) { |
| | | switch ($this->type) { |
| | | case 'shop': |
| | | $title = 'tattoo-shop'; |
| | | break; |
| | | default: |
| | | $title = 'artist'; |
| | | break; |
| | | } |
| | | $title = match ($this->type) { |
| | | 'shop' => 'tattoo-shop', |
| | | default => 'artist', |
| | | }; |
| | | // Use FileUploadManager to generate a filename |
| | | |
| | | $user_data = get_userdata($this->data['user_id']); |
| | |
| | | * @param string $color Center color |
| | | * @return void |
| | | */ |
| | | protected function createGradientBackgroundImagick(Imagick &$canvas, string $color): void |
| | | protected function createGradientBackgroundImagick(Imagick $canvas, string $color): void |
| | | { |
| | | $width = $canvas->getImageWidth(); |
| | | $height = $canvas->getImageHeight(); |
| | |
| | | * @param string $color Center color |
| | | * @return void |
| | | */ |
| | | protected function createGradientBackgroundGD(GdImage &$canvas, string $color):void |
| | | protected function createGradientBackgroundGD(GdImage $canvas, string $color):void |
| | | { |
| | | $width = imagesx($canvas); |
| | | $height = imagesy($canvas); |
| | |
| | | $imageInfo = getimagesize($imagePath); |
| | | |
| | | // Create image from source based on file type |
| | | switch ($imageInfo[2]) { |
| | | case IMAGETYPE_JPEG: |
| | | $sourceImage = imagecreatefromjpeg($imagePath); |
| | | break; |
| | | case IMAGETYPE_PNG: |
| | | $sourceImage = imagecreatefrompng($imagePath); |
| | | break; |
| | | case IMAGETYPE_GIF: |
| | | $sourceImage = imagecreatefromgif($imagePath); |
| | | break; |
| | | case IMAGETYPE_WEBP: |
| | | $sourceImage = imagecreatefromwebp($imagePath); |
| | | break; |
| | | default: |
| | | throw new Exception("Unsupported image type"); |
| | | } |
| | | $sourceImage = match ($imageInfo[2]) { |
| | | IMAGETYPE_JPEG => imagecreatefromjpeg($imagePath), |
| | | IMAGETYPE_PNG => imagecreatefrompng($imagePath), |
| | | IMAGETYPE_GIF => imagecreatefromgif($imagePath), |
| | | IMAGETYPE_WEBP => imagecreatefromwebp($imagePath), |
| | | default => throw new Exception("Unsupported image type"), |
| | | }; |
| | | |
| | | // Calculate dimensions while maintaining aspect ratio |
| | | $sourceWidth = imagesx($sourceImage); |
| | |
| | | use Exception; |
| | | use JVBase\Instance; |
| | | use JVBase\managers\queue\executors\InvitationExecutor; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\managers\queue\TypeConfig; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\base\Site; |
| | |
| | | /** |
| | | * Build invite types from JVB constants |
| | | * Combines JVB_MEMBERSHIP['can_invite'] and invitable taxonomies |
| | | * TODO: Should be accessible via Site::getInstance()->membership()\ |
| | | * |
| | | */ |
| | | protected function buildInviteTypes(): array |
| | | { |
| | | $invitable = $this->cache->remember( |
| | | return $this->cache->remember( |
| | | 'invitableTypes', |
| | | function () { |
| | | $types = []; |
| | |
| | | return $types; |
| | | } |
| | | ); |
| | | return $invitable; |
| | | |
| | | } |
| | | |
| | |
| | | |
| | | // Grant capabilities |
| | | $user = get_userdata($userID); |
| | | $user->add_cap('skip_moderation', true); |
| | | $user->add_cap('skip_moderation'); |
| | | |
| | | return true; |
| | | }); |
| | |
| | | use Exception; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\rest\routes\ApprovalRoutes; |
| | | use JVBase\rest\routes\VoteRoutes; |
| | | use WP_Error; |
| | | use WP_Post; |
| | | use WP_Term; |
| | | use WP_User; |
| | | use WP_Query; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | |
| | | }; |
| | | } |
| | | |
| | | public function maybeJoinKarmaOrderby(string $join, \WP_Query $query): string |
| | | public function maybeJoinKarmaOrderby(string $join, WP_Query $query): string |
| | | { |
| | | if (!in_array($query->get('orderby'), ['upvotes', 'downvotes', 'karmic_score'])) { |
| | | return $join; |
| | |
| | | return $join; |
| | | } |
| | | |
| | | public function maybeApplyKarmaOrderby(string $orderby, \WP_Query $query): string |
| | | public function maybeApplyKarmaOrderby(string $orderby, WP_Query $query): string |
| | | { |
| | | $expr = $this->karmaOrderExpr($query->get('orderby')); |
| | | if (!$expr) { |
| | |
| | | <?php |
| | | namespace JVBase\managers; |
| | | |
| | | use JVBase\base\Site; |
| | | use JetBrains\PhpStorm\NoReturn;use JVBase\base\Site; |
| | | use JVBase\forms\TaxonomySelector; |
| | | use JVBase\Instance;use JVBase\integrations\services\Cloudflare;use JVBase\meta\Form; |
| | | |
| | |
| | | $this->routes = new LoginRoutes(); |
| | | // $this->cache = Cache::for('login'); |
| | | // $this->cache->flush(); |
| | | // Initialize magic link support if enabled |
| | | if (Site::has('magic_link')) { |
| | | $this->initMagicLinkSupport(); |
| | | } |
| | | |
| | | // Create login page if it doesn't exist |
| | | $this->ensureLoginPageExists(); |
| | |
| | | $config = $registrar->getConfig('register'); |
| | | $icon = $registrar->getIcon('user'); |
| | | $icon = ($icon !== '') ? jvbIcon($icon) : ''; |
| | | $select[$role] = '<span class="label">'.$icon.$registrar->getSingular().'</span><span class="text">'.$config['description']??Site::login()->getDescription('register')??''.'</span>'; |
| | | $desc = $config['description']??Site::login()->getDescription('register')??''; |
| | | $select[$role] = '<span class="label">'.$icon.$registrar->getSingular().'</span><span class="text">'.$desc.'</span>'; |
| | | if (!empty($config['fields'])){ |
| | | foreach ($config['fields'] as $field) { |
| | | $field['condition'] = [ |
| | |
| | | return $self->isLoginPage(); |
| | | } |
| | | |
| | | protected function initMagicLinkSupport(): void |
| | | { |
| | | if (!Site::has('magic_link')) { |
| | | return; |
| | | } |
| | | } |
| | | |
| | | /********************************************************************* |
| | | RENDERING |
| | | *********************************************************************/ |
| | |
| | | protected function getAction():string |
| | | { |
| | | if (array_key_exists('action', $_GET)) { |
| | | switch ($_GET['action']){ |
| | | case 'lostpassword': |
| | | case 'retrievepassword': // Alias |
| | | $action = 'lostpassword'; |
| | | break; |
| | | case 'rp': |
| | | case 'resetpass': |
| | | $action = 'resetpass'; |
| | | break; |
| | | default: |
| | | $action = $_GET['action']; |
| | | } |
| | | $action=match($_GET['action']){'lostpassword','retrievepassword'=>'lostpassword','rp','resetpass'=>'resetpass',default=>$_GET['action'],}; |
| | | } else { |
| | | $action = 'login'; |
| | | } |
| | |
| | | |
| | | protected function setupTitle():void |
| | | { |
| | | switch ($this->action) { |
| | | case 'lostpassword': |
| | | $title = 'Lost Your Password?'; |
| | | break; |
| | | case 'resetpass': |
| | | $title = 'Reset Your Password'; |
| | | break; |
| | | case 'register': |
| | | $title = 'Create Your Account'; |
| | | break; |
| | | default: |
| | | $title = 'Log In To Your Account'; |
| | | } |
| | | $title=match($this->action){'lostpassword'=>'Lost Your Password?','resetpass'=>'Reset Your Password','register'=>'Create Your Account',default=>'Log In To Your Account',}; |
| | | $this->title = $title; |
| | | } |
| | | |
| | |
| | | |
| | | protected function getDefaultLabels(): array |
| | | { |
| | | switch ($this->action) { |
| | | case 'register': |
| | | return Site::login()->getLabels('register'); |
| | | case 'lostpassword': |
| | | return Site::login()->getLabels('lostPassword'); |
| | | case 'resetpass': |
| | | case 'rp': |
| | | return Site::login()->getLabels('resetPassword'); |
| | | case 'logout': |
| | | return Site::login()->getLabels('logout'); |
| | | case 'magic': |
| | | return Site::login()->getLabels('magic'); |
| | | case 'login': |
| | | default: |
| | | return Site::login()->getLabels('login'); |
| | | } |
| | | return match($this->action){'register'=>Site::login()->getLabels('register'),'lostpassword'=>Site::login()->getLabels('lostPassword'),'resetpass','rp'=>Site::login()->getLabels('resetPassword'),'logout'=>Site::login()->getLabels('logout'),'magic'=>Site::login()->getLabels('magic'),default=>Site::login()->getLabels('login'),}; |
| | | } |
| | | |
| | | protected function maybeMagicLink(): void |
| | |
| | | /** |
| | | * Handle login errors |
| | | */ |
| | | protected function handleLoginError(WP_Error $error): void |
| | | #[NoReturn]protected function handleLoginError(WP_Error $error): void |
| | | { |
| | | $login_url = wp_login_url(); |
| | | $login_url = add_query_arg('login_error', urlencode($error->get_error_code()), $login_url); |
| | |
| | | /** |
| | | * Process login via magic link |
| | | */ |
| | | #[NoReturn] |
| | | protected function processLogin(array $token_data): void |
| | | { |
| | | $user = get_user_by('ID', $token_data['user_id']); |
| | |
| | | /** |
| | | * Process password reset via magic link |
| | | */ |
| | | #[NoReturn] |
| | | protected function processPasswordReset(array $token_data): void |
| | | { |
| | | $user = get_user_by('ID', $token_data['user_id']); |
| | |
| | | <?php |
| | | namespace JVBase\managers; |
| | | |
| | | use JVBase\JVB; |
| | | use JVBase\managers\queue\Queue; |
| | | use WP_Post; |
| | | use WP_Term; |
| | | use WP_Query; |
| | |
| | | */ |
| | | class NewsRelationships |
| | | { |
| | | private CustomTable $relationships; |
| | | protected Cache $cache; |
| | | |
| | | public function __construct() |
| | |
| | | <?php |
| | | namespace JVBase\managers\Notifications; |
| | | |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\CustomTable; |
| | | use JVBase\managers\Emails; |
| | | use JVBase\managers\NotificationManager; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Error; |
| | | use WP_Query; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | |
| | | 'page' => 1, |
| | | ]); |
| | | |
| | | $args = array_merge([ |
| | | return array_merge([ |
| | | 'where' => $where, |
| | | ],$order); |
| | | |
| | | return $args; |
| | | } |
| | | |
| | | public function getUserNotifications(int $user_id, array $args = []):array |
| | |
| | | use WP_Error; |
| | | use WP_REST_Response; |
| | | use WP_REST_Request; |
| | | use wpdb; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | |
| | | protected string $table = BASE.'_operation_queue'; |
| | | protected string $metricsTable = BASE.'stats__operation_queue'; |
| | | protected int $max_attempts = 3; |
| | | protected \wpdb $wpdb; |
| | | protected wpdb $wpdb; |
| | | |
| | | // System load thresholds |
| | | protected array $load_thresholds = [ |
| | |
| | | <?php |
| | | namespace JVBase\managers; |
| | | |
| | | use Exception; |
| | | use JVBase\Instance; |
| | | use JVBase\integrations\services\Cloudflare; |
| | | use JVBase\meta\Form; |
| | |
| | | 'From ' . $yesterday |
| | | ); |
| | | |
| | | $content .= Emails::getInstance()->spacer(20); |
| | | $content .= Emails::getInstance()->spacer(); |
| | | $content .= Emails::getInstance()->h2('New Referrals'); |
| | | |
| | | // Build list of referrals |
| | |
| | | Emails::getInstance()->stat($from_user->referral_count, 'Total Referrals'), |
| | | Emails::getInstance()->stat($from_user->treated_count, 'Treated') |
| | | ]; |
| | | $cardContent .= Emails::getInstance()->grid($stats, 2); |
| | | $cardContent .= Emails::getInstance()->grid($stats); |
| | | |
| | | $content .= Emails::getInstance()->card($cardContent); |
| | | $rank++; |
| | |
| | | */ |
| | | protected function generateCSV(array $referrals): string |
| | | { |
| | | $cache = Cache::for('referralCSV', null, HOUR_IN_SECONDS); |
| | | $cache = Cache::for('referralCSV'); |
| | | return $cache->remember( |
| | | 'csv', |
| | | function () use ($referrals) { |
| | |
| | | } |
| | | |
| | | // Check if code is unique |
| | | if ($this->isCodeTaken($code, $user_id)) { |
| | | if ($this->isCodeTaken($code)) { |
| | | add_action('user_profile_update_errors', function($errors) { |
| | | $errors->add('referral_code_taken', |
| | | 'This referral code is already in use.'); |
| | |
| | | Cloudflare::getInstance()->renderTurnstile(); |
| | | $turnstile = ob_get_clean(); |
| | | |
| | | $reward_text = $this->getRewardText(true); |
| | | $reward_text = $this->getRewardText(); |
| | | |
| | | // Pre-fill code if from referral link |
| | | $prefill_code = $_GET['ref'] ?? ''; |
| | |
| | | 'message' => 'Referral settings saved successfully!' |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => 'Failed to save settings: ' . $e->getMessage() |
| | |
| | | |
| | | use JVBase\Instance; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\rest\routes\ResponseRoutes; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\base\Site; |
| | | use WP_User; |
| | | use WP_Role; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | |
| | | |
| | | $owned = []; |
| | | foreach ($user->allcaps as $cap => $value) { |
| | | if ($value && strpos($cap, BASE . 'can_own_') === 0) { |
| | | if ($value && str_starts_with($cap, BASE . 'can_own_')) { |
| | | $termID = (int) str_replace(BASE . 'can_own_', '', $cap); |
| | | if ($termID) { |
| | | $owned[] = $termID; |
| | |
| | | |
| | | $managed = []; |
| | | foreach ($user->allcaps as $cap => $value) { |
| | | if ($value && (strpos($cap, BASE . 'can_manage_') === 0 || |
| | | strpos($cap, BASE . 'can_own_') === 0)) { |
| | | if ($value && (str_starts_with($cap, BASE . 'can_manage_') || |
| | | str_starts_with($cap, BASE . 'can_own_'))) { |
| | | $termID = (int) str_replace([BASE . 'can_manage_', BASE . 'can_own_'], '', $cap); |
| | | if ($termID && !in_array($termID, $managed)) { |
| | | $managed[] = $termID; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO; |
| | | |
| | | use JVBase\admin\SEOAdmin; |
| | | use JVBase\Instance; |
| | | use JVBase\managers\AdminPages; |
| | | use JVBase\meta\Form; |
| | | use JVBase\registrar\Registrar; |
| | |
| | | // SITE-WIDE SCHEMA CONFIGURATION |
| | | // ================================================== |
| | | |
| | | use JVBase\meta\Meta; |
| | | |
| | | add_filter('jvb_schema', function ($schema) { |
| | | return array_merge($schema, [ |
| | | 'site_type' => 'directory', |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\DataType; |
| | | |
| | | use DateMalformedStringException; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | protected string $date; |
| | | |
| | | /** |
| | | * @throws \DateMalformedStringException |
| | | * @throws DateMalformedStringException |
| | | */ |
| | | public function __construct(string $date) |
| | | { |
| | |
| | | |
| | | $registrar = false; |
| | | if (is_front_page()) { |
| | | $schema[] = $this->buildWebsiteSchema(true); |
| | | $schema[] = $this->buildWebsiteSchema(); |
| | | $test = $this->buildOrganizationSchema(); |
| | | if (!empty($test)) { |
| | | $schema[] = $test; |
| | |
| | | use JVBase\managers\SEO\render\Traits\_Properties\actionProcessTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\actionStatusTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\agentTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\descriptionTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\endTimeTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\errorTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\instrumentTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\locationTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\nameTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\objectTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\participantTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\providerTrait; |
| | |
| | | <?php |
| | | namespace JVBase\inc\managers\SEO\render\Thing\Action; |
| | | |
| | | use JVBase\managers\SEO\render\Traits\_Properties\actionProcessTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\actionStatusTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\agentTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\endTimeTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\errorTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\instrumentTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\locationTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\objectTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\participantTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\providerTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\resultTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\startTimeTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\targetTrait; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\hasDefinedTermTrait; |
| | | |
| | | |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\HowToSupply; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\HowToTool; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\MonetaryAmount; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Organization\Organization; |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Thing\Person\Person; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\estimateCostTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\performTimeTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\prepTimeTrait; |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\MonetaryAmount; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\MonetaryAmount; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible; |
| | | |
| | | use JVBase\managers\SEO\render\DataType\Date; |
| | | use JVBase\managers\SEO\render\DataType\DateTime; |
| | | use JVBase\managers\SEO\render\DataType\Time; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\AdultOrientedEnumeration; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\BusinessFunction; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DeliveryMethod; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\ItemAvailability; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\OfferItemCondition; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\PhysicalActivityCategory; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Rating\AggregateRating; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PriceSpecification; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PropertyValue; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea; |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\highPriceTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\lowPriceTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\offerCountTrait; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\CategoryCodeSet; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\codeValueTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\inCodeSetTrait; |
| | | |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\DefinedTermSet; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\aboutTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\inDefinedTermSetTrait; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\DefinedTermSet; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DigitalPlatformEnumeration; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\actionPlatformTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\contentTypeTrait; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Schedule; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\OpeningHoursSpecification; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\ServicePeriod; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\requiredQuantityTrait; |
| | | |
| | |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\MonetaryAmount; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\requiredQuantityTrait; |
| | | |
| | | |
| | |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible; |
| | | |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\RestrictedDiet; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\NutritionalInformation; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\menuAddOnTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\nutritionTrait; |
| | |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible; |
| | | |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\RestrictedDiet; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\NutritionalInformation; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\StructuredValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\ThingSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| New file |
| | |
| | | <?php |
| | | |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Quantity; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | | |
| | | class DurationOfWarranty extends Thing |
| | | { |
| | | public function __construct(string $duration) |
| | | { |
| | | $this->duration = date('c', $duration); |
| | | } |
| | | |
| | | /** |
| | | * @var string Duration (use ISO 8601 duration format). |
| | | */ |
| | | protected string $duration; |
| | | } |
| | |
| | | } |
| | | require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Quantity/Distance.php'); |
| | | require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Quantity/Duration.php'); |
| | | require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Quantity/DurationOfWarranty.php'); |
| | | require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Quantity/Mass.php'); |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\Reservation; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Organization\Organization; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Thing\Person\Person; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\bookingTimeTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceCurrencyTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\providerTrait; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\maxValueTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\minValueTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\propertyIDTrait; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue; |
| | | |
| | | use JVBase\managers\SEO\render\DataType\Time; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DayOfWeek; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\businessDaysTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\cutoffTimeTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\durationTrait; |
| | |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\ThingSchema; |
| | | |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\WarrantyScope; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\durationOfWarrantyTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\warrantyScopeTrait; |
| | | |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Organization\Organization; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | use JVBase\managers\SEO\render\Traits\Place\PlaceSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait; |
| | | use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Place\AdministrativeArea; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Rating\AggregateRating; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\LocationFeatureSpecification; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PropertyValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Place\AdministrativeArea; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Place\AdministrativeArea; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Thing\Place\AdministrativeArea; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress; |
| | | use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | use JVBase\managers\SEO\render\DataType\DateTime; |
| | | use JVBase\managers\SEO\render\DataType\Time; |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\ImageObject; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PropertyValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\additionalTypeTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\alternateNameTrait; |
| | |
| | | use JVBase\managers\SEO\render\Traits\_Properties\sameAsTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\subjectOfTrait; |
| | | use JVBase\managers\SEO\render\Traits\_Properties\urlTrait; |
| | | use ReflectionClass; |
| | | |
| | | trait ThingSchema { |
| | | use additionalTypeTrait, alternateNameTrait, descriptionTrait, |
| | |
| | | |
| | | public function getTypeName():string |
| | | { |
| | | return (new \ReflectionClass($this))->getShortName(); |
| | | return (new ReflectionClass($this))->getShortName(); |
| | | } |
| | | |
| | | public function getProperties():array |
| | |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\AudioObject; |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\Clip; |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\Clip; |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\MusicRecording; |
| | | use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper; |
| | | |
| | |
| | | $byMonthWeek = array_filter($byMonthWeek, |
| | | function($week) { |
| | | if ($week === 0 || $week > 6) { |
| | | error_log('[SEO] Invalid month number: '.$month); |
| | | error_log('[SEO] Invalid month number: '.$week); |
| | | return false; |
| | | } |
| | | return true; |
| | |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm; |
| | | use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | use JVBase\managers\SEO\render\Thing\Organization\Organization; |
| | | use JVBase\managers\SEO\render\Thing\Person\Person; |
| | | use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper; |
| | | use JVBase\registrar\config\seo\Resolver; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use DateMalformedStringException; |
| | | use JVBase\managers\SEO\render\DataType\Date; |
| | | use JVBase\managers\SEO\render\DataType\DateTime; |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * @throws \DateMalformedStringException |
| | | * @throws DateMalformedStringException |
| | | */ |
| | | public function setDateCreated(Date|DateTime|string $dateCreated):void |
| | | { |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use DateMalformedStringException; |
| | | use JVBase\managers\SEO\render\DataType\Date; |
| | | use JVBase\managers\SEO\render\DataType\DateTime; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | } |
| | | |
| | | /** |
| | | * @throws \DateMalformedStringException |
| | | * @throws DateMalformedStringException |
| | | */ |
| | | public function setDeathDate(Date|string $deathDate):void |
| | | { |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Quantity\DurationOfWarranty; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use DateMalformedStringException; |
| | | use JVBase\managers\SEO\render\DataType\Date; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | } |
| | | |
| | | /** |
| | | * @throws \DateMalformedStringException |
| | | * @throws DateMalformedStringException |
| | | */ |
| | | public function setFoundingDate(Date|string $foundingDate):void |
| | | { |
| | |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\Menu; |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection; |
| | | use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\ContactPoint; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\VirtualHomeLocation; |
| | | use JVBase\managers\SEO\render\Thing\Place\Place; |
| | | use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Service; |
| | | use JVBase\managers\SEO\render\Thing\Product\Product; |
| | | use JVBase\managers\SEO\render\Thing\Product\ProductGroup; |
| | | use JVBase\managers\SEO\render\Thing\Product\ProductModel; |
| | | use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\Intangible\CategoryCode; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\PhysicalActivityCategory; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper; |
| | | |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use JVBase\inc\managers\SEO\render\Thing\Intangible\ItemList\OfferCatalog; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Demand; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Offer; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Service; |
| | |
| | | if ($hasAny->have_posts()) { |
| | | $offer = new Offer(); |
| | | $typeRegistrar = Registrar::getInstance($type); |
| | | |
| | | |
| | | } |
| | | } |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | |
| | | use JVBase\managers\SEO\render\DataType\Date; |
| | | use JVBase\managers\SEO\render\DataType\DateTime; |
| | | use JVBase\managers\SEO\render\DataType\Time; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\managers\SEO\render\Traits\_Properties; |
| | | |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\Clip; |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\Clip; |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\VideoObject; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | |
| | | use Exception; |
| | | use JVBase\Instance; |
| | | use JVBase\managers\queue\Queue; |
| | | use WP_Error; |
| | | use WP_Post; |
| | | use WP_Query; |
| | |
| | | namespace JVBase\managers; |
| | | |
| | | use JVBase\Instance; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Post; |
| | | use WP_Error; |
| | | use Exception; |
| | | use WP_Query; |
| | |
| | | |
| | | use JVBase\Instance; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\managers\CustomTable; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\rest\routes\ApprovalRoutes; |
| | | |
| | |
| | | <?php |
| | | |
| | | use JVBase\managers\Approvals; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\IconsManager; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\base\Site; |
| | |
| | | <?php |
| | | namespace JVBase\managers\queue; |
| | | use DateTimeInterface; |
| | | use wpdb; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | final class Metrics |
| | | { |
| | | public function __construct( |
| | | private \wpdb $db, |
| | | private wpdb $db, |
| | | private string $queueTable, |
| | | private string $statsTable |
| | | ) {} |
| | |
| | | /* ---------- Historical metrics (stats table) ---------- */ |
| | | |
| | | public function dailySummary( |
| | | \DateTimeInterface $from, |
| | | \DateTimeInterface $to |
| | | DateTimeInterface $from, |
| | | DateTimeInterface $to |
| | | ): array { |
| | | $sql = " |
| | | SELECT |
| | |
| | | |
| | | |
| | | public function dailyByType( |
| | | \DateTimeInterface $from, |
| | | \DateTimeInterface $to |
| | | DateTimeInterface $from, |
| | | DateTimeInterface $to |
| | | ): array { |
| | | $sql = " |
| | | SELECT |
| | |
| | | <?php |
| | | namespace JVBase\managers\queue; |
| | | use Throwable; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | $op->result = $result->result; |
| | | $op->completedAt = current_time('mysql'); |
| | | |
| | | } catch (\Throwable $e) { |
| | | } catch (Throwable $e) { |
| | | error_log("[Processor] Exception caught: " . $e->getMessage()); |
| | | $this->handleFailure($op, $e); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | } catch (\Throwable $e) { |
| | | } catch (Throwable $e) { |
| | | error_log("[Processor] Chunk {$index} failed: " . $e->getMessage()); |
| | | |
| | | // Record failed items from this chunk |
| | |
| | | return $chunks; |
| | | } |
| | | |
| | | private function handleFailure(Operation $op, \Throwable $e): void |
| | | private function handleFailure(Operation $op, Throwable $e): void |
| | | { |
| | | $hash = md5($e->getMessage()); |
| | | |
| | |
| | | exit; |
| | | } |
| | | |
| | | use Exception; |
| | | use JVBase\Instance; |
| | | use JVBase\managers\AdminPages; |
| | | use JVBase\managers\Errors; |
| | | use JVBase\rest\routes\QueueRoutes; |
| | | use Throwable; |
| | | use WP_Error; |
| | | use WP_REST_Request; |
| | | use WP_REST_Response; |
| | |
| | | 'updated_existing' => false, |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | Errors::getInstance()->log('queue', $e->getMessage(), $data, 'high'); |
| | | return new WP_Error('queue_failed', $e->getMessage()); |
| | | } |
| | |
| | | /** |
| | | * Try to merge incoming operation into an existing pending/scheduled one. |
| | | * Returns result array if merged, null if not. |
| | | * @throws \Throwable |
| | | * @throws Throwable |
| | | */ |
| | | private function tryMerge(Operation $incoming): ?array |
| | | { |
| | |
| | | public function maintenance(): void |
| | | { |
| | | $this->locker->withLock(function () { |
| | | $this->storage->resetStuckOperations(30); |
| | | $this->storage->resetStuckOperations(); |
| | | }); |
| | | $this->runQueueOnShutdown(); |
| | | } |
| | |
| | | /** |
| | | * Get a specific value from an operation - backwards compatibility |
| | | */ |
| | | public function getOperationValue(string $id, string $column, bool $decodeJson = true): mixed |
| | | public function getOperationValue(string $id, string $column, bool $decodeJson = true): \static|string|int|array|null |
| | | { |
| | | $op = $this->get($id); |
| | | if (!$op) { |
| | |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\CustomTable; |
| | | use LogicException; |
| | | use Throwable; |
| | | |
| | | class Storage |
| | | { |
| | |
| | | $result = $callback(); |
| | | $this->table->commit(); |
| | | return $result; |
| | | } catch (\Throwable $e) { |
| | | } catch (Throwable $e) { |
| | | $this->table->rollback(); |
| | | error_log('[Storage] Transaction rolled back: ' . $e->getMessage()); |
| | | throw $e; |
| | |
| | | } |
| | | |
| | | /** |
| | | * @throws \Throwable |
| | | * @throws Throwable |
| | | */ |
| | | public function replaceDependency(string $fromId, string $toId): int |
| | | { |
| | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | | |
| | | use InvalidArgumentException; |
| | | use JVBase\managers\queue\mergers\DefaultMerger; |
| | | |
| | | final class TypeRegistry |
| | |
| | | if ($config->executor && $config->chunkKey) { |
| | | // Verify executor can handle chunked operations |
| | | if (!method_exists($config->executor, 'execute')) { |
| | | throw new \InvalidArgumentException( |
| | | throw new InvalidArgumentException( |
| | | "Executor for '{$type}' must implement execute() method" |
| | | ); |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\managers\queue\executors; |
| | | |
| | | use JVBase\managers\queue\{Executor, Operation, Progress, Result, Storage}; |
| | | use DateTime; |
| | | use JVBase\managers\queue\{Executor, Operation, Progress, Result}; |
| | | use JVBase\managers\Errors; |
| | | use JVBase\managers\Roles; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\Registrar; |
| | | use Exception; |
| | | use WP_Post; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | ]; |
| | | |
| | | private int $userId; |
| | | private string $postType; |
| | | private array $fields = []; |
| | | |
| | | public function execute(Operation $operation, Progress $progress): Result |
| | | { |
| | |
| | | // } |
| | | |
| | | $outcome = 'success'; |
| | | if (!empty($errors)) { |
| | | $outcome = count($errors) === count($posts) ? 'failed' : 'partial'; |
| | | if (!empty($results['errors'])) { |
| | | $outcome = count($results['success']) === count($posts) ? 'failed' : 'partial'; |
| | | } |
| | | |
| | | return new Result( |
| | |
| | | } |
| | | } |
| | | |
| | | private function calculateTimeline(\WP_POST $previous, \WP_POST $current): ?string |
| | | private function calculateTimeline(WP_POST $previous, WP_POST $current): ?string |
| | | { |
| | | $previousDate = strtotime($previous->post_date); |
| | | $currentDate = strtotime($current->post_date); |
| | |
| | | return $weeks === 1 ? '1 Week' : $weeks . ' Weeks'; |
| | | } |
| | | |
| | | $previousDateTime = new \DateTime($previous->post_date); |
| | | $currentDateTime = new \DateTime($current->post_date); |
| | | $previousDateTime = new DateTime($previous->post_date); |
| | | $currentDateTime = new DateTime($current->post_date); |
| | | |
| | | $interval = $previousDateTime->diff($currentDateTime); |
| | | $months = ($interval->y * 12) + $interval->m; |
| | |
| | | return ($months === 1) ? '1 Month' : $months . ' Months'; |
| | | } |
| | | |
| | | private function swapTimelineParent(\WP_Post $oldParent, \WP_Post $newParent, array $allPosts): void |
| | | private function swapTimelineParent(WP_Post $oldParent, WP_Post $newParent, array $allPosts): void |
| | | { |
| | | // Swap titles and content |
| | | $originalTitle = $oldParent->post_title; |
| | |
| | | ]); |
| | | |
| | | // Clear timeline taxonomy from new parent |
| | | wp_set_object_terms($newParent->ID, [], BASE . 'timeline', false); |
| | | wp_set_object_terms($newParent->ID, [], BASE . 'timeline'); |
| | | |
| | | // Update all other posts to new parent |
| | | foreach ($allPosts as $index => $post) { |
| | |
| | | namespace JVBase\managers\queue\executors; |
| | | |
| | | use JVBase\managers\CustomTable; |
| | | use JVBase\managers\NotificationManager; |
| | | use JVBase\managers\queue\Executor; |
| | | use JVBase\managers\queue\Operation; |
| | | use JVBase\managers\queue\Progress; |
| | |
| | | $term = get_term($termID, jvbCheckBase($taxonomy)); |
| | | $user = get_userdata($userID); |
| | | |
| | | foreach ($managers as $managerID) { |
| | | NotificationManager::getInstance()->addNotification( |
| | | $managerID, |
| | | $notificationType, |
| | | [ |
| | | 'user_id' => $userID, |
| | | 'user_name' => $user ? $user->display_name : 'Unknown', |
| | | 'term_id' => $termID, |
| | | 'term_name' => $term && !is_wp_error($term) ? $term->name : 'Unknown', |
| | | 'taxonomy' => $taxonomy |
| | | ] |
| | | ); |
| | | } |
| | | //TODO: Check this |
| | | NotificationManager::getInstance()->notify( |
| | | $managers, |
| | | $notificationType, |
| | | $userID, |
| | | [ |
| | | 'target_id' => $termID, |
| | | 'target_type' => $taxonomy |
| | | ] |
| | | ); |
| | | |
| | | } |
| | | } |
| | |
| | | |
| | | use JVBase\managers\queue\{Executor, Operation, Progress, Result}; |
| | | use Exception; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\integrations\ServiceMap; |
| | | use JVBase\managers\Errors; |
| | | |
| | |
| | | <?php |
| | | namespace JVBase\managers\queue\executors; |
| | | |
| | | use Exception; |
| | | use JVBase\managers\CustomTable; |
| | | use JVBase\managers\queue\Executor; |
| | | use JVBase\managers\queue\Operation; |
| | |
| | | $this->table->rollback(); |
| | | } |
| | | |
| | | return Result::success($results, [ |
| | | 'sent' => count($results['success']), |
| | | 'failed' => count($results['failed']) |
| | | ]); |
| | | return Result::success($results); |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->table->rollback(); |
| | | return Result::fail($e->getMessage()); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | $progress->advance(1); |
| | | $progress->advance(); |
| | | |
| | | return new Result( |
| | | outcome: 'success', |
| | |
| | | |
| | | $this->applyMeta($attachmentId, $info); |
| | | $updatedCount++; |
| | | $progress->advance(1); |
| | | $progress->advance(); |
| | | |
| | | } catch (Exception $e) { |
| | | $success = false; |
| | |
| | | if (!empty($file['temp_path']) && file_exists($file['temp_path'])) { |
| | | @unlink($file['temp_path']); |
| | | } |
| | | $progress->advance(1); |
| | | $progress->advance(); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | $usedUploads = array_merge($usedUploads, $created['usedUploads']); |
| | | $progress->advance(1); |
| | | $progress->advance(); |
| | | } |
| | | } catch (Exception $e) { |
| | | $errors[] = $e->getMessage(); |
| | |
| | | if (array_key_exists($uploadId, $uploads)) { |
| | | $attachmentId = (int)$uploads[$uploadId]['attachment_id']; |
| | | if ($uploadId === $featured) { |
| | | $featuredID = $attachmentId;; |
| | | $featuredID = $attachmentId; |
| | | } else { |
| | | $childPosts[] = $attachmentId; |
| | | } |
| | |
| | | |
| | | public static function renderExistingAttachment(int $attachmentId, string $subtype):string |
| | | { |
| | | switch ($subtype){ |
| | | case 'video': |
| | | return static::renderVideoPreview($attachmentId); |
| | | case 'document': |
| | | case 'file': |
| | | return static::renderFilePreview($attachmentId); |
| | | default: |
| | | return static::renderImagePreview($attachmentId); |
| | | } |
| | | return match ($subtype) { |
| | | 'video' => static::renderVideoPreview($attachmentId), |
| | | 'document', 'file' => static::renderFilePreview($attachmentId), |
| | | default => static::renderImagePreview($attachmentId), |
| | | }; |
| | | } |
| | | protected static function renderUploadItemStart(?int $attachmentId = null):string |
| | | { |
| | |
| | | } |
| | | public static function renderImagePreview(?int $ID = null, ?array $additionalFields = null, bool $isTemplate = false):string |
| | | { |
| | | $out = static::renderUploadItemStart($ID, $isTemplate); |
| | | $out = static::renderUploadItemStart($ID); |
| | | //add image preview |
| | | if ($ID) { |
| | | $out .= jvbFormatImage($ID, 'tiny', 'medium'); |
| | |
| | | $plural |
| | | ); |
| | | break; |
| | | default: |
| | | $attr = false; |
| | | $single = false; |
| | | $plural = false; |
| | | } |
| | | |
| | | if (!$attr || !$single || !$plural) { |
| | | return ''; |
| | | } |
| | | $dataAttrs = []; |
| | | if ($config['update']) { |
| | | $dataAttrs[] = 'data-update="false"'; |
| | |
| | | $values = array_filter(array_values($values)); |
| | | return implode(', ', $values) ?: 'New Item'; |
| | | default: |
| | | if (strpos($tagFormat, '{') !== false) { |
| | | if (str_contains($tagFormat, '{')) { |
| | | $text = $tagFormat; |
| | | foreach ($values as $key => $value) { |
| | | $text = str_replace('{'.$key.'}', $value, $text); |
| | |
| | | } |
| | | $this->data[$field] = $value; |
| | | } |
| | | $this->sync(true); |
| | | $this->sync(); |
| | | return $this; |
| | | } |
| | | |
| | |
| | | */ |
| | | public function toArray(): array |
| | | { |
| | | $data = []; |
| | | foreach ($this->fields as $name => $field) { |
| | | $data[$name] = $field->value; |
| | | } |
| | | $data = array_map(function ($field) { |
| | | return $field->value; |
| | | }, $this->fields); |
| | | return $data; |
| | | } |
| | | } |
| | |
| | | /** |
| | | * Get default value for a field type |
| | | */ |
| | | protected function getDefaultValue(string $name): mixed |
| | | protected function getDefaultValue(string $name): string|int|bool|array |
| | | { |
| | | $config = $this->item->getFieldConfig($name); |
| | | $type = $config['type'] ?? 'text'; |
| | |
| | | protected function getFieldSchema(array $field): array |
| | | { |
| | | if ($field['type'] === 'repeater') { |
| | | $properties = []; |
| | | foreach ($field['fields'] as $key => $subfield) { |
| | | $properties[$key] = [ |
| | | 'type' => MetaTypeManager::getMetaType($subfield['type']) |
| | | ]; |
| | | } |
| | | $properties = array_map(function ($subfield) { |
| | | return [ |
| | | 'type' => MetaTypeManager::getMetaType($subfield['type']) |
| | | ]; |
| | | }, $field['fields']); |
| | | |
| | | return [ |
| | | 'type' => 'object', |
| | |
| | | use Exception; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\Errors; |
| | | use wpdb; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | */ |
| | | class Storage |
| | | { |
| | | protected \wpdb $wpdb; |
| | | protected wpdb $wpdb; |
| | | |
| | | public function __construct() |
| | | { |
| | |
| | | 'item_id' => $item->id, |
| | | 'object_type' => $item->objectType, |
| | | 'fields' => array_keys($dirty) |
| | | ], 'error'); |
| | | ]); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | $config = $item->getFieldConfig($name); |
| | | if ($config && ($config['type'] ?? '') === 'taxonomy' && !isset($config['taxonomy_type'])) { |
| | | $taxonomy = jvbCheckBase($config['taxonomy']); |
| | | wp_set_object_terms($item->id, [], $taxonomy, false); |
| | | wp_set_object_terms($item->id, [], $taxonomy); |
| | | return true; |
| | | } |
| | | |
| | |
| | | $results[$meta->item()->id ?? 'options'] = false; |
| | | } |
| | | |
| | | Errors::getInstance()->log('meta_storage', 'Bulk save failed: ' . $e->getMessage(), [], 'error'); |
| | | Errors::getInstance()->log('meta_storage', 'Bulk save failed: ' . $e->getMessage()); |
| | | |
| | | return $results; |
| | | } |
| | |
| | | $taxonomy = jvbCheckBase($field->config['taxonomy']); |
| | | $value = $field->value; |
| | | if (empty(trim((string)$value))) { |
| | | wp_set_object_terms($item->id, [], $taxonomy, false); |
| | | wp_set_object_terms($item->id, [], $taxonomy); |
| | | return true; |
| | | } |
| | | |
| | | $termIds = array_map('intval', array_filter(explode(',', $value))); |
| | | $result = wp_set_object_terms($item->id, $termIds, $taxonomy, false); |
| | | $result = wp_set_object_terms($item->id, $termIds, $taxonomy); |
| | | |
| | | return !is_wp_error($result); |
| | | } |
| | |
| | | ? [] |
| | | : array_map('intval', array_filter(explode(',', $update['value']))); |
| | | |
| | | wp_set_object_terms($update['object_id'], $termIds, $update['taxonomy'], false); |
| | | wp_set_object_terms($update['object_id'], $termIds, $update['taxonomy']); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | class Fields { |
| | | protected array $fields; |
| | | private ?Registrar $registrar = null; |
| | | private ?Registrar $registrar; |
| | | protected ?string $type; |
| | | |
| | | public function __construct(?string $type = null, ?Registrar $registrar = null) { |
| | |
| | | namespace JVBase\registrar; |
| | | |
| | | use JVBase\forms\TaxonomySelector; |
| | | use JVBase\managers\Errors; |
| | | use WP_Post; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | // Use 'bottom' priority so taxonomy rules match first |
| | | add_rewrite_rule( |
| | | "{$this->unbased}/([^/]+)/?$", |
| | | "index.php?post_type={$this->postType}&name=\$matches[1]", |
| | | 'bottom' |
| | | "index.php?post_type={$this->postType}&name=\$matches[1]" |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * Set $this->rewrite_taxonomy to a valid taxonomy |
| | | * @param string $url |
| | | * @param \WP_Post $post |
| | | * @param WP_Post $post |
| | | * @return string |
| | | */ |
| | | public function rewriteTaxonomySingle(string $url, \WP_Post $post): string |
| | | public function rewriteTaxonomySingle(string $url, WP_Post $post): string |
| | | { |
| | | if ($post->post_type === $this->postType && !is_null($this->taxonomyRewrite)) { |
| | | $type = $this->taxonomyRewrite; |
| | |
| | | public function modifyWooCommerce() { |
| | | if ($this->modify) { |
| | | $args = $this->buildArgs(); |
| | | unset($args['labels']);; |
| | | unset($args['labels']); |
| | | $registered = get_post_type_object($this->postType); |
| | | |
| | | foreach ($args as $key => $value) { |
| | |
| | | $exclude = $cache->remember( |
| | | $feature, |
| | | function () use ($instance, $feature) { |
| | | switch ($feature) { |
| | | case 'hide_single': |
| | | return $instance->excludeSingle(); |
| | | case 'is_timeline': |
| | | return $instance->excludeTimeline(); |
| | | default: |
| | | return []; |
| | | } |
| | | return match ($feature) { |
| | | 'hide_single' => $instance->excludeSingle(), |
| | | 'is_timeline' => $instance->excludeTimeline(), |
| | | default => [], |
| | | }; |
| | | } |
| | | ); |
| | | |
| | |
| | | error_log('Integration not available for '.$this->slug.': '.$integration); |
| | | return $this; |
| | | } |
| | | $this->integrationConfigs[$integration] = new Integration($integration, $this); |
| | | $this->integrationConfigs[$integration] = new Integration($integration); |
| | | $this->integrationFields[$integration] = new AddIntegrationFields($integration, $this); |
| | | return $this; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\registrar\config; |
| | | |
| | | use JVBase\registrar\Registrar; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\registrar\config; |
| | | |
| | | use JVBase\registrar\Registrar; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | |
| | | function jvbTSFDoIt(string $slug, ?array $args):bool |
| | | { |
| | | if (!class_exists('THE_SEO_Framework')) { |
| | | if (!class_exists('THE_SEO_Framework') || !function_exists('tsf')) { |
| | | return false; |
| | | } |
| | | $tsf = tsf(); |
| | |
| | | |
| | | $meta = new \JVBase\meta\Meta($ID, Registrar::getInstance($this->slug)->getType()); |
| | | $resolved = Resolver::resolve($this->title, $meta); |
| | | $resolved = $this->testLength($ID, $resolved, 'title'); |
| | | return $resolved; |
| | | return $this->testLength($ID, $resolved, 'title'); |
| | | } |
| | | |
| | | public function filterDescription(string $description, ?array $args): string |
| | |
| | | namespace JVBase\registrar\config\seo; |
| | | |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\SEO\render\SchemaOutput; |
| | | use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\ImageObject; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Distance; |
| | | use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue; |
| | | use JVBase\managers\SEO\render\Thing\Thing; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\Registrar; |
| | | |
| | |
| | | class Resolver { |
| | | protected static ?Meta $meta; |
| | | |
| | | public static function resolve(string $template, ?Meta $meta): string|array|\JVBase\managers\SEO\render\Thing\Thing |
| | | public static function resolve(string $template, ?Meta $meta): string|array|Thing |
| | | { |
| | | if (!$meta) { |
| | | if (is_singular()){ |
| | |
| | | |
| | | public function define(string $property, string $value):void |
| | | { |
| | | $class = $this->getConfig('schema')['type']; |
| | | $class = $this->getConfig()['type']; |
| | | if (!class_exists($class)) { |
| | | error_log('[SEO]Schema::defineReference Class not found: '.$class); |
| | | return; |
| | |
| | | |
| | | foreach ($config as $key => $value) { |
| | | if (property_exists($class, $key)) { |
| | | $method = 'set'.implode('',array_map('ucfirst',explode('_', $key)));; |
| | | $method = 'set'.implode('',array_map('ucfirst',explode('_', $key))); |
| | | $class->$method($value); |
| | | } else { |
| | | // error_log('Instance: '.print_r($class, true)); |
| | |
| | | namespace JVBase\registrar\fields; |
| | | |
| | | |
| | | use ReflectionClass; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | $fields = array_filter(array_map(function($field) { return str_replace('}}','',$field);}, explode('{{',$variables))); |
| | | foreach ($fields as $field) { |
| | | if (!array_key_exists($field, $this->fields)) { |
| | | error_log('['.(new \ReflectionClass($this))->getShortName().']Invalid field attempted: '.$field); |
| | | error_log('['.(new ReflectionClass($this))->getShortName().']Invalid field attempted: '.$field); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | <?php |
| | | namespace JVBase\inc\registrar\helpers; |
| | | |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Post; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | <?php |
| | | namespace JVBase\registrar\helpers; |
| | | |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\Registrar; |
| | | use WP_Post; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | private array $data = []; |
| | | private int $status = 200; |
| | | private array $headers = []; |
| | | private ?WP_REST_Response $response = null; |
| | | |
| | | /** |
| | | /** |
| | | * Create a success response |
| | | */ |
| | | public static function success(array $data = [], int $status = 200): WP_REST_Response |
| | |
| | | <?php |
| | | namespace JVBase\rest; |
| | | |
| | | use JVBase\Instance; |
| | | use JVBase\integrations\services\Cloudflare; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\Errors; |
| | |
| | | if (!array_key_exists($taxonomy, $terms)) { |
| | | $terms[$taxonomy] = []; |
| | | $registrar = Registrar::getInstance($taxonomy); |
| | | $terms[$taxonomy]['icon'] = $registrar ? $registrar->getIcon() : jvbDefaultIcon();; |
| | | $terms[$taxonomy]['icon'] = $registrar ? $registrar->getIcon() : jvbDefaultIcon(); |
| | | } |
| | | foreach ($ids as $id) { |
| | | $data = $cache->remember( |
| | |
| | | * @param string $nonceName nonce name to check |
| | | * @param string|null $nonce optional additional nonce to check |
| | | * |
| | | * @return void|WP_Error |
| | | */ |
| | | public function verifyNonce(WP_REST_Request $request, string $nonceName = 'wp_rest', string|null $nonce = null):mixed |
| | | { |
| | | * @return bool|WP_Error |
| | | */ |
| | | public function verifyNonce(WP_REST_Request $request, string $nonceName = 'wp_rest', string|null $nonce = null): bool|WP_Error |
| | | { |
| | | $nonce = (!is_null($nonce)) ? $nonce : $request->get_header('X-WP-Nonce'); |
| | | |
| | | if (!wp_verify_nonce($nonce, $nonceName)) { |
| | |
| | | return false; |
| | | } |
| | | // Verify nonces |
| | | $this->verifyNonce($request, 'wp_rest'); |
| | | $this->verifyNonce($request); |
| | | if ($this->action!=='') { |
| | | $this->verifyNonce($request, $this->action . $user_id, $request->get_header('action_nonce')); |
| | | } |
| | |
| | | $context, |
| | | 'info' |
| | | ); |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | // Fallback to error_log if handler fails |
| | | error_log("Security Audit: {$event} - " . json_encode($context)); |
| | | } |
| | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | | } |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\base\Site; |
| | | |
| | | //NEW METHOD |
| | | require(JVB_DIR . '/inc/rest/Route.php'); |
| | |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\Instance; |
| | | use JVBase\JVB; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\IconsManager; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\rest\Route; |
| | | use JVBase\rest\PermissionHandler; |
| | | use WP_Query; |
| | | use WP_Error; |
| | | use WP_REST_Request; |
| | | use WP_REST_Response; |
| | | |
| | |
| | | /** |
| | | * Handle cache-related actions |
| | | */ |
| | | public function handleCacheAction(\WP_REST_Request $request): \WP_REST_Response |
| | | public function handleCacheAction(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $action = sanitize_text_field($request->get_param('action')); |
| | | |
| | | switch ($action) { |
| | | case 'flush-all': |
| | | $total = Cache::flushAll(); |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => true, |
| | | 'message' => $total.' caches flushed successfully' |
| | | ]); |
| | |
| | | case 'flush-cache': |
| | | $group = sanitize_text_field($request->get_param('group')); |
| | | if (empty($group)) { |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'No cache group specified' |
| | | ], 400); |
| | |
| | | |
| | | Cache::for($group)?->flush(); |
| | | |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => true, |
| | | 'message' => "Cache group '{$group}' flushed successfully" |
| | | ]); |
| | | |
| | | default: |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'Invalid action' |
| | | ], 400); |
| | |
| | | /** |
| | | * Handle icon-related actions |
| | | */ |
| | | public function handleIconAction(\WP_REST_Request $request): \WP_REST_Response |
| | | public function handleIconAction(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $action = sanitize_text_field($request->get_param('action')); |
| | | $source = sanitize_text_field($request->get_param('source') ?? 'icons'); |
| | |
| | | $icons->forceRefresh(); |
| | | IconsManager::regenerateAllCSS([$source => true]); |
| | | |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => true, |
| | | 'message' => "Icon CSS regenerated successfully for '{$source}'" |
| | | ]); |
| | |
| | | } |
| | | IconsManager::regenerateAllCSS(); |
| | | |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => true, |
| | | 'message' => 'All icon CSS files regenerated successfully' |
| | | ]); |
| | |
| | | case 'restore-icon-version': |
| | | $timestamp = (int)$request->get_param('timestamp'); |
| | | if (empty($timestamp)) { |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'No timestamp provided' |
| | | ], 400); |
| | | } |
| | | |
| | | if ($icons->restoreVersion($timestamp)) { |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => true, |
| | | 'message' => 'Icon version restored successfully' |
| | | ]); |
| | | } |
| | | |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'Failed to restore icon version' |
| | | ], 500); |
| | |
| | | $timestamps = $request->get_param('timestamps'); |
| | | |
| | | if (empty($timestamps) || !is_array($timestamps)) { |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'No versions selected for merging' |
| | | ], 400); |
| | |
| | | $timestamps = array_map('intval', $timestamps); |
| | | |
| | | if (count($timestamps) < 2) { |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'Please select at least 2 versions to merge' |
| | | ], 400); |
| | |
| | | // Regenerate CSS after merge |
| | | IconsManager::regenerateAllCSS([$source => true]); |
| | | |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => true, |
| | | 'message' => 'Icon versions merged successfully' |
| | | ]); |
| | | } |
| | | |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'Failed to merge icon versions' |
| | | ], 500); |
| | | |
| | | default: |
| | | return new \WP_REST_Response([ |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'Invalid action' |
| | | ], 400); |
| | |
| | | * @param string $taxonomy Taxonomy |
| | | * @param string $name New Term Name |
| | | * @param int $parent Parent Term ID |
| | | * @param int $required_approvals Number of approvals required |
| | | * |
| | | * |
| | | * @return int|false Request ID or false on failure |
| | | */ |
| | | public function createTermApprovalRequest( |
| | | int $user_id, |
| | | string $taxonomy, |
| | | string $name, |
| | | int $parent = 0, |
| | | int $required_approvals = 3 |
| | | int $parent = 0 |
| | | ): int|false { |
| | | |
| | | $result = Approvals::getInstance()->createApproval( |
| | |
| | | { |
| | | $user_id = get_current_user_id(); |
| | | $type = sanitize_text_field($request->get_param('type') ?? 'all'); |
| | | $status = sanitize_text_field($request->get_param('status') ?? 'pending'); |
| | | // $status = sanitize_text_field($request->get_param('status') ?? 'pending'); |
| | | |
| | | $result = Approvals::getInstance()->getApprovalRequests($user_id, $type === 'all' ? null : $type); |
| | | |
| | |
| | | // Search term is already in $args['s'] from earlier |
| | | |
| | | // Get all taxonomies registered to this post type |
| | | $taxonomies = get_object_taxonomies($this->post_type, 'names'); |
| | | $taxonomies = get_object_taxonomies($this->post_type); |
| | | |
| | | if (empty($taxonomies)) { |
| | | return $args; |
| | |
| | | use JVBase\rest\Rest; |
| | | use JVBase\rest\Route; |
| | | use JVBase\rest\PermissionHandler; |
| | | use JVBase\base\Site; |
| | | use JVBase\managers\CustomTable; |
| | | use WP_REST_Request; |
| | | use WP_REST_Response; |
| | |
| | | $total = $query->countResults(); |
| | | |
| | | $members = $query |
| | | ->orderBy('is_primary', 'DESC') |
| | | ->orderBy('is_primary') |
| | | ->orderBy('created_at', 'ASC') |
| | | ->limit($perPage, $offset) |
| | | ->getResults(ARRAY_A); |
| | |
| | | $total = $query->countResults(); |
| | | |
| | | $requests = $query |
| | | ->orderBy('created_date', 'DESC') |
| | | ->orderBy('created_date') |
| | | ->limit($perPage, $offset) |
| | | ->getResults(ARRAY_A); |
| | | |
| | |
| | | } |
| | | }); |
| | | |
| | | // Notify user |
| | | NotificationManager::getInstance()->addNotification( |
| | | $requestData['user_id'], |
| | | 'request_' . $status, |
| | | [ |
| | | 'term_id' => $requestData['term_id'], |
| | | 'taxonomy' => $this->taxonomy, |
| | | 'notes' => $notes |
| | | ] |
| | | ); |
| | | // Notify user TODO |
| | | // NotificationManager::getInstance()->notify( |
| | | // $requestData['user_id'], |
| | | // 'request_' . $status, |
| | | // [ |
| | | // 'term_id' => $requestData['term_id'], |
| | | // 'taxonomy' => $this->taxonomy, |
| | | // 'notes' => $notes |
| | | // ] |
| | | // ); |
| | | |
| | | $this->cache->flush(); |
| | | |
| | |
| | | |
| | | $term = get_term($termID, jvbCheckBase($this->taxonomy)); |
| | | |
| | | foreach ($managers as $managerID) { |
| | | NotificationManager::getInstance()->addNotification( |
| | | $managerID, |
| | | $notificationType, |
| | | [ |
| | | 'user_id' => $actingUserID, |
| | | 'term_id' => $termID, |
| | | 'term_name' => $term->name ?? 'Unknown', |
| | | 'taxonomy' => $this->taxonomy |
| | | ] |
| | | ); |
| | | } |
| | | //TODO |
| | | // foreach ($managers as $managerID) { |
| | | // NotificationManager::getInstance()->addNotification( |
| | | // $managerID, |
| | | // $notificationType, |
| | | // [ |
| | | // 'user_id' => $actingUserID, |
| | | // 'term_id' => $termID, |
| | | // 'term_name' => $term->name ?? 'Unknown', |
| | | // 'taxonomy' => $this->taxonomy |
| | | // ] |
| | | // ); |
| | | // } |
| | | } |
| | | } |
| | |
| | | use JVBase\Instance; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\CustomTable; |
| | | use JVBase\managers\Emails; |
| | | use JVBase\managers\Favourites; |
| | | use JVBase\managers\NotificationManager; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\PermissionHandler; |
| | | use JVBase\rest\Response; |
| | |
| | | // Get list items |
| | | $items = $this->listItems |
| | | ->where(['list_id' => $list_id]) |
| | | ->orderBy('added_at', 'DESC') |
| | | ->orderBy('added_at') |
| | | ->getResults(); |
| | | |
| | | // Format items - convert to favourite-like objects |
| | |
| | | |
| | | // Get shared users if owner |
| | | $shared_users = []; |
| | | $is_owner = false;//TODO |
| | | if ($is_owner) { |
| | | $shares = $this->listShares |
| | | ->where(['list_id' => $list_id]) |
| | | ->orderBy('created_at', 'DESC') |
| | | ->orderBy('created_at') |
| | | ->getResults(); |
| | | |
| | | foreach ($shares as $share_item) { |
| | |
| | | $shared_users[] = $shared_user; |
| | | } |
| | | } |
| | | |
| | | $list = []; //TODO: check this |
| | | return [ |
| | | 'success' => true, |
| | | 'list' => [ |
| | |
| | | ]); |
| | | |
| | | // Send notification |
| | | NotificationManager::getInstance()->addNotification( |
| | | $share_user->ID, |
| | | 'list_shared', |
| | | $user_id, |
| | | sprintf('%s shared a favorites list with you: "%s"', $owner_name, $list->name), |
| | | $list_id, |
| | | 'favourites_list', |
| | | [ |
| | | 'list_id' => $list_id, |
| | | 'list_name' => $list->name, |
| | | 'permission_type' => $permission_type |
| | | ] |
| | | ); |
| | | // TODO This should be handled by the Favourites:: manager |
| | | // NotificationManager::getInstance()->addNotification( |
| | | // $share_user->ID, |
| | | // 'list_shared', |
| | | // $user_id, |
| | | // sprintf('%s shared a favorites list with you: "%s"', $owner_name, $list->name), |
| | | // $list_id, |
| | | // 'favourites_list', |
| | | // [ |
| | | // 'list_id' => $list_id, |
| | | // 'list_name' => $list->name, |
| | | // 'permission_type' => $permission_type |
| | | // ] |
| | | // ); |
| | | |
| | | return [ |
| | | 'success' => true, |
| | |
| | | // For posts |
| | | if (str_contains($type, 'post') || in_array($type, array_keys($this->valid_types))) { |
| | | $post = get_post($target_id); |
| | | return $post ? $post->post_author : null; |
| | | return $post?->post_author; |
| | | } |
| | | |
| | | // For terms (shops, etc.) |
| | |
| | | }; |
| | | |
| | | if (!$operation_type) { |
| | | return $this->error('Invalid action', 'invalid_action', 400); |
| | | return $this->error('Invalid action', 'invalid_action'); |
| | | } |
| | | |
| | | Queue::getInstance()->queueOperation( |
| | |
| | | 'highlight' => 'string', |
| | | ]) |
| | | ->auth('public') |
| | | ->rateLimit(30, 60) |
| | | ->rateLimit(30) |
| | | ->post([$this, 'handleFeedRequest']) |
| | | ->args([ |
| | | 'content' => 'string', |
| | |
| | | use JVBase\meta\Validator; |
| | | use JVBase\rest\PermissionHandler; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\blocks\FormBlock; |
| | | use JVBase\rest\Route; |
| | | use JVBase\base\Site; |
| | |
| | | if (is_wp_error($result)) { |
| | | return $this->error( |
| | | $result->get_error_message(), |
| | | $result->get_error_code(), |
| | | 400 |
| | | $result->get_error_code() |
| | | ); |
| | | } |
| | | if (array_key_exists('success', $result)){ |
| | |
| | | if (!empty($files)) { |
| | | try { |
| | | $files = $this->validateFileUploads($files, $form_config); |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | return new WP_Error('file_validation_failed', 'File validation error: ' . $e->getMessage()); |
| | | } |
| | | } |
| | |
| | | // Validate and sanitize form data |
| | | try { |
| | | $processed_data = $this->validateAndSanitizeData($form_config, $form_data); |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | return new WP_Error('validation_failed', 'Data validation error: ' . $e->getMessage()); |
| | | } |
| | | if (array_key_exists('success', $processed_data) && $processed_data['success'] === false) { |
| | |
| | | // Send email notification with attachments |
| | | try { |
| | | $email_sent = $this->sendEmailNotification($form_type, $form_config, $processed_data, $files); |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | return new WP_Error('email_failed', 'Email error: ' . $e->getMessage()); |
| | | } |
| | | |
| | |
| | | ]; |
| | | } |
| | | |
| | | $body .= Emails::getInstance()->spacer(20); |
| | | $body .= Emails::getInstance()->spacer(); |
| | | $body .= Emails::getInstance()->table($form); |
| | | |
| | | // Show attachment info |
| | |
| | | if (!empty($attachments)) { |
| | | $body .= Emails::getInstance()->spacer(10); |
| | | $body .= Emails::getInstance()->alert( |
| | | sprintf('%d file(s) attached to this email', count($attachments)), |
| | | 'info' |
| | | sprintf('%d file(s) attached to this email', count($attachments)) |
| | | ); |
| | | } |
| | | |
| | |
| | | $forms = FormBlock::getForms(); |
| | | |
| | | // Remove sensitive data |
| | | $public_forms = []; |
| | | foreach ($forms as $key => $config) { |
| | | $public_forms[$key] = [ |
| | | // Remove sensitive configuration |
| | | $public_forms = array_map(function ($config) use ($field) { |
| | | return [ |
| | | 'title' => $config['title'], |
| | | 'description' => $config['description'], |
| | | 'fields' => array_map(function($field) { |
| | | 'fields' => array_map(function ($field) { |
| | | // Remove sensitive configuration |
| | | unset($field['email_to']); |
| | | return $field; |
| | | }, $config['fields']) |
| | | ]; |
| | | } |
| | | }, $forms); |
| | | |
| | | return $this->success($public_forms); |
| | | } |
| | |
| | | use JVBase\rest\Route; |
| | | use WP_REST_Request; |
| | | use WP_REST_Response; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | // Get uploaded file |
| | | $files = $request->get_file_params(); |
| | | if (empty($files['file'])) { |
| | | return $this->error('No file uploaded', 'no_file', 400); |
| | | return $this->error('No file uploaded', 'no_file'); |
| | | } |
| | | |
| | | $file = $files['file']; |
| | | |
| | | // Validate file type |
| | | if (!$this->isValidCSV($file)) { |
| | | return $this->error('Invalid file type. Please upload a CSV file.', 'invalid_file', 400); |
| | | return $this->error('Invalid file type. Please upload a CSV file.', 'invalid_file'); |
| | | } |
| | | |
| | | // Get and parse options |
| | |
| | | // Get uploaded file |
| | | $files = $request->get_file_params(); |
| | | if (empty($files['file'])) { |
| | | return $this->error('No file uploaded', 'no_file', 400); |
| | | return $this->error('No file uploaded', 'no_file'); |
| | | } |
| | | |
| | | $file = $files['file']; |
| | | |
| | | // Validate file type |
| | | if (!$this->isValidCSV($file)) { |
| | | return $this->error('Invalid file type. Please upload a CSV file.', 'invalid_file', 400); |
| | | return $this->error('Invalid file type. Please upload a CSV file.', 'invalid_file'); |
| | | } |
| | | |
| | | // Get and parse options |
| | |
| | | use JVBase\Instance; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\InvitationsManager; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\managers\Roles; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\managers\CustomTable; |
| | |
| | | <?php |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use Exception; |
| | | use JVBase\Instance; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\Emails; |
| | | use JVBase\managers\MagicLinks; |
| | | use JVBase\managers\Referrals; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\rest\Route; |
| | |
| | | if (is_wp_error($user)) { |
| | | return $this->error( |
| | | 'Invalid or expired reset link.', |
| | | 'invalid_key', |
| | | 400 |
| | | 'invalid_key' |
| | | ); |
| | | } |
| | | |
| | |
| | | if (!Site::has('magic_link')) { |
| | | return $this->error( |
| | | 'Magic link authentication is not enabled.', |
| | | 'feature_disabled', |
| | | 400 |
| | | 'feature_disabled' |
| | | ); |
| | | } |
| | | |
| | |
| | | |
| | | return $this->error( |
| | | $result->get_error_message(), |
| | | $result->get_error_code(), |
| | | 400 |
| | | $result->get_error_code() |
| | | ); |
| | | } |
| | | |
| | |
| | | |
| | | try { |
| | | Referrals::getInstance()->processReferralCode($user_id, $referral_code); |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Referral processing failed', [ |
| | | 'user_id' => $user_id, |
| | | 'code' => $referral_code, |
| | |
| | | */ |
| | | protected function sanitizeFieldValue(mixed $value, string $type):string|int |
| | | { |
| | | switch ($type) { |
| | | case 'email': |
| | | return sanitize_email($value); |
| | | case 'url': |
| | | return esc_url_raw($value); |
| | | case 'textarea': |
| | | return sanitize_textarea_field($value); |
| | | case 'number': |
| | | return absint($value); |
| | | default: |
| | | return sanitize_text_field($value); |
| | | } |
| | | return match ($type) { |
| | | 'email' => sanitize_email($value), |
| | | 'url' => esc_url_raw($value), |
| | | 'textarea' => sanitize_textarea_field($value), |
| | | 'number' => absint($value), |
| | | default => sanitize_text_field($value), |
| | | }; |
| | | } |
| | | |
| | | public static function auth():array |
| | |
| | | use JVBase\managers\MagicLinks; |
| | | use WP_REST_Request; |
| | | use WP_REST_Response; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\Instance; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\rest\PermissionHandler; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\meta\Meta; |
| | |
| | | <?php |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\base\Site; |
| | | use JVBase\Instance; |
| | | use JVBase\managers\Approvals; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\CustomTable; |
| | | use JVBase\managers\NotificationManager; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\rest\Route; |
| | | use WP_REST_Request; |
| | |
| | | 'action_user_id' => $group->action_user_id, |
| | | 'type' => $group->type |
| | | ]) |
| | | ->orderBy('created_at', 'DESC') |
| | | ->orderBy('created_at') |
| | | ->first(); |
| | | |
| | | if ($notification) { |
| | |
| | | */ |
| | | protected function getItemLink(int $ID, string $content): string |
| | | { |
| | | switch ($content) { |
| | | case BASE.'artist': |
| | | case BASE.'artwork': |
| | | case BASE.'event': |
| | | case BASE.'news': |
| | | case BASE.'offer': |
| | | case BASE.'partner': |
| | | case BASE.'piercing': |
| | | case BASE.'tattoo': |
| | | return get_permalink($ID); |
| | | default: |
| | | return get_term_link($ID, BASE.$content); |
| | | } |
| | | return match ($content) { |
| | | BASE . 'artist', BASE . 'artwork', BASE . 'event', BASE . 'news', BASE . 'offer', BASE . 'partner', BASE . 'piercing', BASE . 'tattoo' => get_permalink($ID), |
| | | default => get_term_link($ID, BASE . $content), |
| | | }; |
| | | } |
| | | |
| | | /** |
| | |
| | | return $irregular[$word]; |
| | | } |
| | | |
| | | if (substr($word, -1) === 'y') { |
| | | if (str_ends_with($word, 'y')) { |
| | | return substr($word, 0, -1) . 'ies'; |
| | | } |
| | | |
| | |
| | | |
| | | use JVBase\base\Options; |
| | | use JVBase\Instance; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\rest\PermissionHandler; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\rest\Route; |
| | | use WP_REST_Request; |
| | | use WP_REST_Response; |
| | |
| | | return Response::notFound('Operation not found'); |
| | | } |
| | | |
| | | return Response::item($this->formatOperation($op, true), 'operation'); |
| | | return Response::item($this->formatOperation($op), 'operation'); |
| | | } |
| | | |
| | | |
| | |
| | | 'treated' => $this->actionUpdateStatus($request, 'treated'), |
| | | 'remove' => $this->actionRemove($request), |
| | | 'resend' => $this->actionResend($request), |
| | | default => $this->error('Invalid action', 'invalid_action', 400) |
| | | default => $this->error('Invalid action', 'invalid_action') |
| | | }; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | if (empty($sanitized_invitations)) { |
| | | return $this->error('No valid invitations provided', 'no_invitations', 400); |
| | | return $this->error('No valid invitations provided', 'no_invitations',); |
| | | } |
| | | |
| | | $operationID = sanitize_text_field($request->get_param('id')); |
| | |
| | | |
| | | $referral_id = $request->get_param('referral_id'); |
| | | if (!$referral_id) { |
| | | return $this->error('referral_id required', 'missing_id', 400); |
| | | return $this->error('referral_id required', 'missing_id'); |
| | | } |
| | | |
| | | [$success, $message] = Referrals::getInstance()->updateStatus($referral_id, $status); |
| | |
| | | { |
| | | $referral_id = $request->get_param('referral_id'); |
| | | if (!$referral_id) { |
| | | return $this->error('referral_id required', 'missing_id', 400); |
| | | return $this->error('referral_id required', 'missing_id'); |
| | | } |
| | | |
| | | $result = Referrals::getInstance()->removeReferral($referral_id, get_current_user_id()); |
| | |
| | | { |
| | | $referral_id = $request->get_param('referral_id'); |
| | | if (!$referral_id) { |
| | | return $this->error('referral_id required', 'missing_id', 400); |
| | | return $this->error('referral_id required', 'missing_id'); |
| | | } |
| | | |
| | | $result = Referrals::getInstance()->resendInvitation($referral_id, get_current_user_id()); |
| | |
| | | $code = Referrals::getInstance()->getUserReferralCode($user_id); |
| | | |
| | | if (is_wp_error($code)) { |
| | | return $this->error($code->get_error_message(), 'code_error', 400); |
| | | return $this->error($code->get_error_message(), 'code_error'); |
| | | } |
| | | |
| | | return $this->success([ |
| | |
| | | $code = strtoupper(sanitize_text_field($request->get_param('code')??'')); |
| | | |
| | | if (empty($code)) { |
| | | return $this->error('Code required', 'missing_code', 400); |
| | | return $this->error('Code required', 'missing_code'); |
| | | } |
| | | |
| | | $referrer = Referrals::getInstance()->getUserByReferralCode($code); |
| | |
| | | public function handleClientUpload(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | if (empty($_FILES['file'])) { |
| | | return $this->error('No file uploaded', 'no_file', 400); |
| | | return $this->error('No file uploaded', 'no_file'); |
| | | } |
| | | |
| | | $file = $_FILES['file']; |
| | | |
| | | if ($file['error'] !== UPLOAD_ERR_OK) { |
| | | return $this->error('File upload error: ' . $file['error'], 'upload_error', 400); |
| | | return $this->error('File upload error: ' . $file['error'], 'upload_error'); |
| | | } |
| | | |
| | | // Validate file type |
| | |
| | | finfo_close($finfo); |
| | | |
| | | if (!in_array($mime_type, $allowed_types) && !in_array($file['type'], $allowed_types)) { |
| | | return $this->error('File must be a CSV', 'invalid_file_type', 400); |
| | | return $this->error('File must be a CSV', 'invalid_file_type'); |
| | | } |
| | | |
| | | // Validate file size (10MB max) |
| | | if ($file['size'] > 10 * 1024 * 1024) { |
| | | return $this->error('File size exceeds 10MB limit', 'file_too_large', 400); |
| | | return $this->error('File size exceeds 10MB limit', 'file_too_large'); |
| | | } |
| | | |
| | | // Import using JaneAppClientImporter |
| | |
| | | public function handleSalesUpload(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | if (empty($_FILES['file'])) { |
| | | return $this->error('No file uploaded', 'no_file', 400); |
| | | return $this->error('No file uploaded', 'no_file'); |
| | | } |
| | | |
| | | $file = $_FILES['file']; |
| | | |
| | | if ($file['error'] !== UPLOAD_ERR_OK) { |
| | | return $this->error('File upload error: ' . $file['error'], 'upload_error', 400); |
| | | return $this->error('File upload error: ' . $file['error'], 'upload_error'); |
| | | } |
| | | |
| | | // Validate file type |
| | |
| | | finfo_close($finfo); |
| | | |
| | | if (!in_array($mime_type, $allowed_types) && !in_array($file['type'], $allowed_types)) { |
| | | return $this->error('File must be a CSV', 'invalid_file_type', 400); |
| | | return $this->error('File must be a CSV', 'invalid_file_type'); |
| | | } |
| | | |
| | | // Validate file size (10MB max) |
| | | if ($file['size'] > 10 * 1024 * 1024) { |
| | | return $this->error('File size exceeds 10MB limit', 'file_too_large', 400); |
| | | return $this->error('File size exceeds 10MB limit', 'file_too_large'); |
| | | } |
| | | |
| | | // Import using JaneAppSalesImporter |
| | |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\Instance; |
| | | use JVBase\JVB; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\NotificationManager; |
| | | use JVBase\managers\Notifications\Notifications; |
| | | use JVBase\managers\Responses; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\managers\CustomTable; |
| | |
| | | use JVBase\base\SchemaHelper; |
| | | use JVBase\Instance; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\SEO\render\SchemaOutput; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\rest\Route; |
| | |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\Instance; |
| | | use JVBase\JVB; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\rest\PermissionHandler; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\managers\Cache; |
| | |
| | | <?php |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\JVB; |
| | | use JVBase\managers\Emails; |
| | | use JVBase\managers\Errors; |
| | | use JVBase\managers\ImageGenerator; |
| | | use JVBase\managers\NotificationManager; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\managers\UploadManager; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\RestRouteManager; |
| | |
| | | $operationID = $data['id']; |
| | | unset($data['id']); |
| | | |
| | | $data = json_encode($data); |
| | | |
| | | $queue->queueOperation( |
| | | 'shop_update', |
| | |
| | | $shop_name = get_term($shopID, BASE . 'shop')->name; |
| | | |
| | | // Notify each manager |
| | | foreach ($owners as $owner) { |
| | | NotificationManager::getInstance()->addNotification( |
| | | $owner, |
| | | 'artist_request', |
| | | [ |
| | | 'artist_id' => $artistID, |
| | | 'artist_name' => $artist_name, |
| | | 'user_id' => $userID, |
| | | 'shop_id' => $shopID, |
| | | 'shop_name' => $shop_name |
| | | ] |
| | | ); |
| | | } |
| | | //TODO |
| | | // foreach ($owners as $owner) { |
| | | // NotificationManager::getInstance()->addNotification( |
| | | // $owner, |
| | | // 'artist_request', |
| | | // [ |
| | | // 'artist_id' => $artistID, |
| | | // 'artist_name' => $artist_name, |
| | | // 'user_id' => $userID, |
| | | // 'shop_id' => $shopID, |
| | | // 'shop_name' => $shop_name |
| | | // ] |
| | | // ); |
| | | // } |
| | | } |
| | | |
| | | /** |
| | |
| | | $invitation_id = $wpdb->insert_id; |
| | | |
| | | // If user exists, send notification through the system |
| | | if ($existing_user_id) { |
| | | NotificationManager::getInstance()->addNotification( |
| | | $existing_user_id, |
| | | 'artist_invitation', |
| | | $manager_id, |
| | | sprintf('You have been invited to join %s', get_term($shop_id, BASE . 'shop')->name), |
| | | $invitation_id, |
| | | 'invitation' |
| | | ); |
| | | } |
| | | //TODO |
| | | // if ($existing_user_id) { |
| | | // NotificationManager::getInstance()->addNotification( |
| | | // $existing_user_id, |
| | | // 'artist_invitation', |
| | | // $manager_id, |
| | | // sprintf('You have been invited to join %s', get_term($shop_id, BASE . 'shop')->name), |
| | | // $invitation_id, |
| | | // 'invitation' |
| | | // ); |
| | | // } |
| | | |
| | | // Send email invitation |
| | | $this->sendInvitationEmail($artist_email, $artist_name, $manager_id, $shop_id, $token); |
| | |
| | | |
| | | // If invitation was for an existing user, notify them |
| | | $user_id = email_exists($invitation->email); |
| | | if ($user_id) { |
| | | NotificationManager::getInstance()->addNotification( |
| | | $user_id, |
| | | 'invitation_revoked', |
| | | $manager_id, |
| | | sprintf('Your invitation to %s has been revoked', get_term($shop_id, BASE . 'shop')->name), |
| | | $shop_id, |
| | | BASE . 'shop' |
| | | ); |
| | | } |
| | | //TODO |
| | | // if ($user_id) { |
| | | // NotificationManager::getInstance()->addNotification( |
| | | // $user_id, |
| | | // 'invitation_revoked', |
| | | // $manager_id, |
| | | // sprintf('Your invitation to %s has been revoked', get_term($shop_id, BASE . 'shop')->name), |
| | | // $shop_id, |
| | | // BASE . 'shop' |
| | | // ); |
| | | // } |
| | | |
| | | return new WP_REST_Response([ |
| | | 'success' => true, |
| | |
| | | |
| | | // Notify the artist |
| | | $notification_type = $status === 'accepted' ? 'shop_accepted' : 'shop_rejected'; |
| | | NotificationManager::getInstance()->addNotification( |
| | | $request->user_id, |
| | | $notification_type, |
| | | [ |
| | | 'shop_id' => $request->shop_id, |
| | | 'shop_name' => get_term($request->shop_id, BASE . 'shop')->name, |
| | | 'notes' => $notes |
| | | ] |
| | | ); |
| | | //TODO |
| | | // NotificationManager::getInstance()->addNotification( |
| | | // $request->user_id, |
| | | // $notification_type, |
| | | // [ |
| | | // 'shop_id' => $request->shop_id, |
| | | // 'shop_name' => get_term($request->shop_id, BASE . 'shop')->name, |
| | | // 'notes' => $notes |
| | | // ] |
| | | // ); |
| | | |
| | | return new WP_REST_Response([ |
| | | 'success' => true, |
| | |
| | | } |
| | | |
| | | // Notify inviters |
| | | $inviters = json_decode($invitation['inviters'], true); |
| | | if (!empty($inviters)) { |
| | | foreach ($inviters as $inviter_id) { |
| | | NotificationManager::getInstance()->addNotification( |
| | | $inviter_id, |
| | | 'invitation_accepted', |
| | | [ |
| | | 'artist_id' => $user_id, |
| | | 'artist_name' => jvbGetUsername($user_id), |
| | | 'shop_id' => $invitation['to_shop'] |
| | | ] |
| | | ); |
| | | } |
| | | } |
| | | //TODO |
| | | // $inviters = json_decode($invitation['inviters'], true); |
| | | // if (!empty($inviters)) { |
| | | // foreach ($inviters as $inviter_id) { |
| | | // NotificationManager::getInstance()->addNotification( |
| | | // $inviter_id, |
| | | // 'invitation_accepted', |
| | | // [ |
| | | // 'artist_id' => $user_id, |
| | | // 'artist_name' => jvbGetUsername($user_id), |
| | | // 'shop_id' => $invitation['to_shop'] |
| | | // ] |
| | | // ); |
| | | // } |
| | | // } |
| | | |
| | | $wpdb->query('COMMIT'); |
| | | return true; |
| | |
| | | [ |
| | | 'user_id' => $user_id, |
| | | 'invitation' => $invitation |
| | | ], |
| | | 'error' |
| | | ); |
| | | ] |
| | | ); |
| | | |
| | | return false; |
| | | } |
| | |
| | | ); |
| | | |
| | | // Notify inviters |
| | | $inviters = json_decode($invitation['inviters'], true); |
| | | if (!empty($inviters)) { |
| | | foreach ($inviters as $inviter_id) { |
| | | NotificationManager::getInstance()->addNotification( |
| | | $inviter_id, |
| | | 'invitation_declined', |
| | | [ |
| | | 'artist_id' => $user_id, |
| | | 'artist_name' => jvbGetUsername($user_id), |
| | | 'shop_id' => $invitation['to_shop'] |
| | | ] |
| | | ); |
| | | } |
| | | } |
| | | //TODO |
| | | // $inviters = json_decode($invitation['inviters'], true); |
| | | // if (!empty($inviters)) { |
| | | // foreach ($inviters as $inviter_id) { |
| | | // NotificationManager::getInstance()->addNotification( |
| | | // $inviter_id, |
| | | // 'invitation_declined', |
| | | // [ |
| | | // 'artist_id' => $user_id, |
| | | // 'artist_name' => jvbGetUsername($user_id), |
| | | // 'shop_id' => $invitation['to_shop'] |
| | | // ] |
| | | // ); |
| | | // } |
| | | // } |
| | | |
| | | return true; |
| | | } catch (Exception $e) { |
| | |
| | | [ |
| | | 'user_id' => $user_id, |
| | | 'invitation' => $invitation |
| | | ], |
| | | 'error' |
| | | ); |
| | | ] |
| | | ); |
| | | |
| | | return false; |
| | | } |
| | |
| | | $shop_meta = Meta::forTerm($shop_id); |
| | | $owners = $shop_meta->getAll(['owner', 'managers']); |
| | | $owners = array_unique(array_merge(explode(',', $owners['owner']),explode(',', $owners['managers']))); |
| | | |
| | | foreach ($owners as $owner_id) { |
| | | if (!empty($owner_id)) { |
| | | NotificationManager::getInstance()->addNotification( |
| | | $owner_id, |
| | | 'artist_left', |
| | | $user_id, |
| | | sprintf('%s has left %s', jvbGetUsername($user_id), $shop_name), |
| | | $shop_id, |
| | | BASE . 'shop' |
| | | ); |
| | | } |
| | | } |
| | | // TODO |
| | | // foreach ($owners as $owner_id) { |
| | | // if (!empty($owner_id)) { |
| | | // NotificationManager::getInstance()->addNotification( |
| | | // $owner_id, |
| | | // 'artist_left', |
| | | // $user_id, |
| | | // sprintf('%s has left %s', jvbGetUsername($user_id), $shop_name), |
| | | // $shop_id, |
| | | // BASE . 'shop' |
| | | // ); |
| | | // } |
| | | // } |
| | | |
| | | return true; |
| | | } catch (Exception $e) { |
| | |
| | | [ |
| | | 'user_id' => $user_id, |
| | | 'shop_id' => $shop_id |
| | | ], |
| | | 'error' |
| | | ); |
| | | ] |
| | | ); |
| | | |
| | | return false; |
| | | } |
| | |
| | | if ($membership && $membership->has('term_approval')) { |
| | | $approval_routes = ApprovalRoutes::getInstance(); |
| | | $request_id = $approval_routes->createTermApprovalRequest( |
| | | $user_id, |
| | | $taxonomy, |
| | | sanitize_title($name), |
| | | absint($parent) |
| | | $user_id, |
| | | $taxonomy, |
| | | sanitize_title($name), |
| | | absint($parent) |
| | | ); |
| | | |
| | | if (!$request_id) { |
| | |
| | | use JVBase\Instance; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\PermissionHandler; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\rest\Route; |
| | | use JVBase\base\Site; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | protected function getCommonTerms(string $taxonomy, ?string $limit = null):array { |
| | | if ($limit) { |
| | | if (Site::has('membership') && $limit === 'user') { |
| | | $manager = new UserTermsManager(); |
| | | $manager = UserTermsManager::getInstance(); |
| | | return $manager->fetchUserTerms($this->user_id, $taxonomy); |
| | | } else { |
| | | $limit = (int)$limit; |
| | |
| | | use JVBase\base\Site; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\integrations\ServiceMap; |
| | | use JVBase\managers\MagicLinks; |
| | | use JVBase\meta\Form; |
| | | use JVBase\meta\Meta; |
| | | |
| | |
| | | private ?string $icon = null; |
| | | private ?Navigation $submenu = null; |
| | | private array $classes = []; |
| | | private array $menuClasses = []; |
| | | private array $attributes = []; |
| | | private array $attributes = []; |
| | | private bool $current = false; |
| | | |
| | | public function __construct(int $id) { |
| | |
| | | <?php |
| | | namespace JVBase\users; |
| | | |
| | | use JVBase\JVB; |
| | | use JVBase\registry\ContentRegistry; |
| | | //use JVBase\registry\ContentRegistry; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | | } |
| | | |
| | | class UserSettings extends ContentRegistry |
| | | { |
| | | protected array $fields; |
| | | protected array $sections = [ |
| | | 'account' => [ |
| | | 'title' => 'Account', |
| | | 'icon' => 'settings', |
| | | ], |
| | | 'notifications' => [ |
| | | 'title' => 'Notifications', |
| | | 'icon' => 'email' |
| | | ], |
| | | 'newsletter' => [ |
| | | 'title' => 'Newsletter', |
| | | 'icon' => 'logo' |
| | | ], |
| | | 'invitations' => [ |
| | | 'title' => 'Invite Artists', |
| | | 'icon' => 'new-user', |
| | | ] |
| | | ]; |
| | | protected array $frequency = [ |
| | | 'never' => 'Never', |
| | | 'daily' => 'Daily', |
| | | 'weekly' => 'Weekly', |
| | | 'monthly' => 'Monthly', |
| | | ]; |
| | | |
| | | public function initType():void |
| | | { |
| | | $this->object_type = 'user'; |
| | | $this->userFields(); |
| | | } |
| | | |
| | | public function getFields():array |
| | | { |
| | | return $this->fields; |
| | | } |
| | | public function getSections():array |
| | | { |
| | | return $this->sections; |
| | | } |
| | | public function userFields():void |
| | | { |
| | | |
| | | $this->fields = [ |
| | | 'display_name' => [ |
| | | 'type' => 'text', |
| | | 'label' => 'Name', |
| | | 'description' => 'Optional. Only used if you allow artists to see your info when you favourite them.', |
| | | 'default' => '', |
| | | 'section' => 'account', |
| | | 'role' => ['ei_enthusiast'] |
| | | ], |
| | | 'user_email' => [ |
| | | 'type' => 'email', |
| | | 'label' => 'Email', |
| | | 'description' => 'Required for any notifications you subscribe to.', |
| | | 'default' => '', |
| | | 'section' => 'account', |
| | | 'role' => ['ei_enthusiast'] |
| | | ], |
| | | 'city' => [ |
| | | 'type' => 'text', |
| | | 'label' => 'City', |
| | | 'description' => 'Optional. Just helps us know where people who use this site are located.', |
| | | 'default' => '', |
| | | 'section' => 'account', |
| | | 'role' => ['ei_enthusiast'] |
| | | ], |
| | | 'notify' => [ |
| | | 'type' => 'true_false', |
| | | 'label' => 'Notify artists when you favourite their work', |
| | | 'description' => 'If you select this, artists will see your name when you favourite their work, else they will just get an anonymous notification.', |
| | | 'default' => false, |
| | | 'section' => 'account', |
| | | ], |
| | | 'dark_mode' => [ |
| | | 'type' => 'true_false', |
| | | 'label' => 'Dark Mode', |
| | | 'default' => false, |
| | | ], |
| | | 'email_digest' => [ |
| | | 'type' => 'radio', |
| | | 'label' => 'Email Summary Frequency', |
| | | 'options' => $this->frequency, |
| | | 'default' => 'never', |
| | | 'section' => 'notifications' |
| | | ], |
| | | 'digest_override' => [ |
| | | 'type' => 'true_false', |
| | | 'label' => 'Apply to all notifications', |
| | | 'description' => 'Override individual notification settings and apply this frequency to all your notifications', |
| | | 'default' => false, |
| | | 'section' => 'notifications' |
| | | ], |
| | | 'notifications' => [ |
| | | 'type' => 'callback', |
| | | 'callback' => 'renderNotifications', |
| | | 'label' => 'Notifications', |
| | | 'section' => 'notifications', |
| | | 'default' => '', |
| | | ], |
| | | 'site_updates' => [ |
| | | 'type' => 'true_false', |
| | | 'label' => 'Site Updates', |
| | | 'description' => 'Get notified when new features are added to edmonton.ink', |
| | | 'default' => false, |
| | | 'section' => 'account', |
| | | ], |
| | | 'newsletter' => [ |
| | | 'type' => 'true_false', |
| | | 'label' => 'Newsletter', |
| | | 'description' => 'Sign up for our monthly-ish newsletter, featuring upcoming events, featured artists, new artists, and their work.', |
| | | 'default' => false, |
| | | 'section' => 'newsletter', |
| | | ], |
| | | 'owner_of' => [ |
| | | 'type' => 'selector', |
| | | 'subtype'=> 'taxonomy', |
| | | 'label' => __('Owner of', 'jvb'), |
| | | 'isReference' => true, |
| | | 'taxonomy' => BASE. 'shop', |
| | | 'quickEdit' => true, |
| | | 'default' => '', |
| | | ], |
| | | 'manager_of' => [ |
| | | 'type' => 'selector', |
| | | 'subtype' => 'taxonomy', |
| | | 'label' => __('Manager of', 'jvb'), |
| | | 'isReference' => true, |
| | | 'taxonomy' => BASE. 'shop', |
| | | 'hidden' => true, |
| | | 'default' => '', |
| | | ], |
| | | |
| | | 'invitations' => [ |
| | | 'type' => 'callback', |
| | | 'callback' => 'handleInvitationsManagement', |
| | | 'section' => 'invitations', |
| | | 'label' => __('Invite Artists', 'jvb'), |
| | | 'role' => ['ei_artist'] |
| | | ], |
| | | ]; |
| | | } |
| | | |
| | | /** |
| | | * Render custom notification preferences UI |
| | | * @param int $user_id |
| | | * |
| | | * @return string |
| | | */ |
| | | public function renderNotifications(int $user_id):string |
| | | { |
| | | $favourites = $this->getUserFavourites($user_id); |
| | | $preferences = $this->getUserNotificationPreferences($user_id); |
| | | global $notify; |
| | | |
| | | ob_start(); |
| | | ?> |
| | | <div class="notification-preferences"> |
| | | <?php foreach ($favourites as $type => $items) : ?> |
| | | <details class="notification-group"> |
| | | <summary class="notification-group-header row x-btw"> |
| | | <span class="type-label"><?=jvbIcon('heart', ['style' => 'fill'])?> <?= $notify[$type] ?></span> |
| | | <span class="type-count">( <?= count($items); ?> )</span> |
| | | </summary> |
| | | |
| | | <?php if (!empty($items)) : ?> |
| | | <table class="notification-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>Name</th> |
| | | <th>Frequency</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <?php foreach ($items as $item) : ?> |
| | | <?php |
| | | $current = isset($preferences['preferences'][$type][$item['id']]) |
| | | ? $preferences['preferences'][$type][$item['id']] |
| | | : $preferences['default_frequency']; |
| | | ?> |
| | | <tr> |
| | | <td><?= esc_html($item['name']); ?></td> |
| | | <td> |
| | | <div class="radio-options row"> |
| | | <?php foreach (['never', 'daily', 'weekly', 'monthly'] as $freq) : ?> |
| | | <input type="radio" |
| | | name="notification_preferences[<?= esc_attr($type); ?>][<?= esc_attr($item['id']); ?>]" |
| | | id="<?= esc_attr("{$type}_{$item['id']}_$freq"); ?>" |
| | | value="<?= esc_attr($freq); ?>" |
| | | <?php checked($current, $freq); ?>> |
| | | <label for="<?= esc_attr("{$type}_{$item['id']}_$freq"); ?>"> |
| | | <?= ucfirst($freq); ?> |
| | | </label> |
| | | <?php endforeach; ?> |
| | | </div> |
| | | </td> |
| | | </tr> |
| | | <?php endforeach; ?> |
| | | </tbody> |
| | | <tfoot> |
| | | <tr> |
| | | |
| | | <th>Set all <?= $type ?>s to: </th> |
| | | <th> |
| | | <div class="radio-options row"> |
| | | <?php foreach (['never', 'daily', 'weekly', 'monthly'] as $freq) : ?> |
| | | <input type="radio" |
| | | name="toggle-all-<?=$type?>" |
| | | id="toggle-all-<?=$type?>-<?=$freq?>" |
| | | value="<?= esc_attr($freq); ?>" |
| | | class="toggle-all"> |
| | | <label for="toggle-all-<?=$type?>-<?=$freq?>"> |
| | | <?= ucfirst($freq); ?> |
| | | </label> |
| | | <?php endforeach; ?> |
| | | </div> |
| | | </th> |
| | | |
| | | </tr> |
| | | </tfoot> |
| | | </table> |
| | | <?php else : ?> |
| | | <p class="no-items">No favourites yet</p> |
| | | <?php endif; ?> |
| | | </details> |
| | | <?php endforeach; ?> |
| | | </div> |
| | | <script> |
| | | function initGroupOverrides() { |
| | | document.querySelectorAll('.toggle-all').forEach(toggle => { |
| | | toggle.addEventListener('change', e => { |
| | | e.preventDefault(); |
| | | if(toggle.checked){ |
| | | const table = e.target.closest('table'); |
| | | console.log(table); |
| | | const value = toggle.value; |
| | | |
| | | // Enable/disable individual controls |
| | | table.querySelectorAll('input[value="'+value+'"]').forEach(radio => { |
| | | radio.checked = true; |
| | | }); |
| | | } |
| | | |
| | | }); |
| | | }); |
| | | |
| | | // Listen for changes to default frequency |
| | | document.querySelector('input[name="digest_override"]').addEventListener('change', e=>{ |
| | | let value = document.querySelector('input[name="email_digest"]:checked').value; |
| | | |
| | | console.log(value); |
| | | document.querySelectorAll('input[value="'+value+'"]').forEach(toggle => { |
| | | toggle.checked = true; |
| | | }); |
| | | |
| | | setTimeout(function () { |
| | | e.target.checked = false; |
| | | }, 1000) |
| | | |
| | | }); |
| | | |
| | | } |
| | | initGroupOverrides(); |
| | | </script> |
| | | <?php |
| | | return ob_get_clean(); |
| | | } |
| | | |
| | | /** |
| | | * Get user's favourites grouped by type |
| | | */ |
| | | protected function getUserFavourites(int $user_id):array |
| | | { |
| | | global $wpdb; |
| | | $table = $wpdb->prefix . BASE.'favourites'; |
| | | global $notify; |
| | | global $types; |
| | | |
| | | |
| | | $favourites = []; |
| | | foreach (array_keys($notify) as $type) { |
| | | $favourites[$type] = []; |
| | | } |
| | | |
| | | // Fetch favourites from database |
| | | $results = $wpdb->get_results($wpdb->prepare( |
| | | "SELECT * FROM $table WHERE user_id = %d", |
| | | $user_id |
| | | )); |
| | | |
| | | foreach ($results as $result) { |
| | | $type = str_replace(BASE, '', $result->type); |
| | | |
| | | |
| | | if (!array_key_exists($type, $favourites)) { |
| | | continue; |
| | | } |
| | | // Get proper name based on type |
| | | switch ($types[BASE.$type]['table']) { |
| | | case 'post': |
| | | $name = get_the_title($result->target_id); |
| | | break; |
| | | case 'term': |
| | | $term = get_term($result->target_id, BASE.$type); |
| | | $name = $term ? html_entity_decode($term->name) : ''; |
| | | break; |
| | | default: |
| | | $name = ''; |
| | | } |
| | | |
| | | if ($name) { |
| | | $favourites[$type][] = [ |
| | | 'id' => $result->target_id, |
| | | 'name' => $name |
| | | ]; |
| | | } |
| | | } |
| | | |
| | | return $favourites; |
| | | } |
| | | |
| | | /** |
| | | * Get current notification preferences |
| | | */ |
| | | protected function getNotificationPreferences(int $user_id):array |
| | | { |
| | | return get_user_meta($user_id, 'notification_preferences', true) ?: []; |
| | | } |
| | | |
| | | protected function getUserNotificationPreferences(int $user_id):array |
| | | { |
| | | global $wpdb; |
| | | $table = $wpdb->prefix . BASE.'notification_preferences'; |
| | | |
| | | // error_log('Getting notification preferences for user: ' . $user_id); |
| | | |
| | | // Get user's preferences from the table |
| | | $raw_preferences = $wpdb->get_results($wpdb->prepare( |
| | | "SELECT notification_type, item_id, frequency |
| | | FROM {$table} |
| | | WHERE user_id = %d", |
| | | $user_id |
| | | ), ARRAY_A); |
| | | |
| | | // error_log('Raw preferences from database: ' . print_r($raw_preferences, true)); |
| | | |
| | | // Structure the preferences |
| | | $preferences = []; |
| | | if (!empty($raw_preferences)) { |
| | | foreach ($raw_preferences as $pref) { |
| | | $type = $pref['notification_type']; |
| | | $item_id = intval($pref['item_id']); |
| | | $frequency = $pref['frequency']; |
| | | |
| | | if (!isset($preferences[$type])) { |
| | | $preferences[$type] = []; |
| | | } |
| | | $preferences[$type][$item_id] = $frequency; |
| | | } |
| | | } |
| | | |
| | | // Get default frequency |
| | | $default_frequency = get_user_meta($user_id, BASE.'email_digest', true) ?: 'never'; |
| | | |
| | | // error_log('Structured preferences: ' . print_r($preferences, true)); |
| | | // error_log('Default frequency: ' . $default_frequency); |
| | | |
| | | return [ |
| | | 'preferences' => $preferences, |
| | | 'default_frequency' => $default_frequency |
| | | ]; |
| | | } |
| | | |
| | | public function handleInvitationsManagement(int $userID):void |
| | | { |
| | | $name = 'artist_invite'; |
| | | ?> |
| | | <div class="field"> |
| | | <script type="text/javascript"> |
| | | function addArtist(button){ |
| | | let template = window.getTemplate('artistInvite'); |
| | | let container = button.closest('.field').querySelector('.invite-artists'); |
| | | |
| | | let nameLabel = template.querySelector('label[for="invited-artist-name"]'), |
| | | nameInput = template.querySelector('input#invited-artist-name'), |
| | | emailLabel = template.querySelector('label[for="invited-artist-email"]'), |
| | | emailInput = template.querySelector('input#invited-artist-email'), |
| | | shopLabel = template.querySelector('label[for="invited-add-to-shop"]'), |
| | | shopInput = template.querySelector('input#invited-add-to-shop'); |
| | | |
| | | |
| | | let count = container.childNodes.length; |
| | | console.log(container.childNodes); |
| | | [ |
| | | nameLabel.htmlFor, |
| | | nameInput.id, |
| | | nameInput.name, |
| | | emailLabel.htmlFor, |
| | | emailInput.id, |
| | | emailInput.name, |
| | | shopLabel.htmlFor, |
| | | shopInput.id, |
| | | shopInput.name |
| | | ] = |
| | | [ |
| | | `${nameLabel.getAttribute('for')}-${count}`, |
| | | `${nameInput.id}-${count}`, |
| | | `${nameInput.name}-${count}`, |
| | | `${emailLabel.htmlFor}-${count}`, |
| | | `${emailInput.id}-${count}`, |
| | | `${emailInput.name}-${count}`, |
| | | `${shopLabel.htmlFor}-${count}`, |
| | | `${shopInput.id}-${count}`, |
| | | `${shopInput.name}-${count}` |
| | | ]; |
| | | container.append(template); |
| | | } |
| | | function removeArtist(button){ |
| | | button.closest('.invited-artist').remove(); |
| | | } |
| | | |
| | | async function revokeInvite(button){ |
| | | try { |
| | | let artist = button.closest('tr'); |
| | | let id = artist.id; |
| | | |
| | | const headers = { |
| | | 'X-WP-Nonce': eiSettings.nonce, |
| | | 'action_nonce': eiSettings.dash, |
| | | 'Content-Type': 'application/json' |
| | | }; |
| | | |
| | | let body = { |
| | | user: eiSettings.currentUser, |
| | | revoke: id |
| | | }; |
| | | let request = { |
| | | method: 'POST', |
| | | headers: headers, |
| | | body: JSON.stringify(body) |
| | | }; |
| | | console.log(request); |
| | | |
| | | |
| | | const response = await fetch(`${eiSettings.api}invitations`, request); |
| | | |
| | | console.log(response); |
| | | if (!response.ok) { |
| | | const errorData = await response.json().catch(() => ({})); |
| | | } |
| | | |
| | | const data = await response.json(); |
| | | if(data.success){ |
| | | artist.remove(); |
| | | } |
| | | }catch (error) { |
| | | } |
| | | } |
| | | |
| | | async function resendInvite(button){ |
| | | try { |
| | | let artist = button.closest('tr'); |
| | | let id = artist.id; |
| | | |
| | | const headers = { |
| | | 'X-WP-Nonce': eiSettings.nonce, |
| | | 'action_nonce': eiSettings.dash, |
| | | 'Content-Type': 'application/json' |
| | | }; |
| | | |
| | | let body = { |
| | | user: eiSettings.currentUser, |
| | | refresh: id |
| | | }; |
| | | let request = { |
| | | method: 'POST', |
| | | headers: headers, |
| | | body: JSON.stringify(body) |
| | | }; |
| | | console.log(request); |
| | | |
| | | |
| | | const response = await fetch(`${eiSettings.api}invitations`, request); |
| | | |
| | | console.log(response); |
| | | if (!response.ok) { |
| | | const errorData = await response.json().catch(() => ({})); |
| | | } |
| | | |
| | | const data = await response.json(); |
| | | |
| | | }catch (error) { |
| | | } |
| | | } |
| | | |
| | | function sendInvites(button){ |
| | | if(document.querySelector('.invited-artist')){ |
| | | let newArtists = []; |
| | | document.querySelectorAll('.invited-artist').forEach(invited => { |
| | | let name = invited.querySelector('input[type="text"]'), |
| | | email = invited.querySelector('input[type="email"]'), |
| | | toShop = invited.querySelector('input[type="checkbox"]'); |
| | | if(name.value === '' || email.value === ''){ |
| | | invited.classList.add('required'); |
| | | return; |
| | | } |
| | | |
| | | newArtists.push({ |
| | | name: name.value, |
| | | email: email.value, |
| | | toShop: toShop.checked |
| | | }); |
| | | invited.remove(); |
| | | }); |
| | | if(newArtists.length > 0){ |
| | | window.queueManager.addToQueue({ |
| | | type: 'invite_artist', |
| | | data: newArtists |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <section class="invite-artists"> |
| | | <div class="invited-artist"> |
| | | <button type="button" onclick="removeArtist(this)" title="Remove Invite"><?=jvbIcon('trash')?></button> |
| | | <div> |
| | | <label for="invited-artist-name-1">First Name</label> |
| | | <input type="text" id="invited-artist-name-1" name="invited-artist-name-1"> |
| | | </div> |
| | | <div> |
| | | <label for="invited-artist-email-1">Email</label> |
| | | <input type="email" id="invited-artist-email-1" name="invited-artist-email-1"> |
| | | </div> |
| | | <div> |
| | | <input type="checkbox" name="invited-add-to-shop-1" id="invited-add-to-shop-1"> |
| | | <label for="invited-add-to-shop-1">Add to shop?</label> |
| | | </div> |
| | | </div> |
| | | </section> |
| | | <div class="actions"> |
| | | <button type="button" onclick="addArtist(this)" class="add-artist"><?= jvbIcon('plus-square')?>Add Artist</button> |
| | | <button type="button" onclick="sendInvites(this)" class="send-invites"><?=jvbIcon('envelope')?>Send Invites</button> |
| | | </div> |
| | | |
| | | <template class="artistInvite"> |
| | | <div class="invited-artist"> |
| | | <button type="button" onclick="removeArtist(this)" title="Remove Invite"><?=jvbIcon('trash')?></button> |
| | | <div> |
| | | <label for="invited-artist-name">First Name</label> |
| | | <input type="text" id="invited-artist-name" name="invited-artist-name"> |
| | | </div> |
| | | <div> |
| | | <label for="invited-artist-email">Email</label> |
| | | <input type="email" id="invited-artist-email" name="invited-artist-email"> |
| | | </div> |
| | | <div> |
| | | <input type="checkbox" name="invited-add-to-shop" id="invited-add-to-shop"> |
| | | <label for="invited-add-to-shop">Add to shop?</label> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | <?php |
| | | |
| | | $invites = JVB()->routes('invites')->getUserInvitations($userID); |
| | | |
| | | ?> |
| | | <table class="artist-invite"> |
| | | <thead> |
| | | <tr> |
| | | <th scope="col" style="width:100%;">Name</th> |
| | | <th scope="col" style="padding:0 .5rem">Email</th> |
| | | <th scope="col" style="padding:0 .5rem">Status</th> |
| | | <th scope="col" style="padding:0 .5rem">Expires</th> |
| | | <th scope="col" style="padding:0 .5rem">Actions</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <?php |
| | | if (!empty($invites->invitations)) { |
| | | foreach ($invites->invitations as $invite) { |
| | | ?> |
| | | <tr id="<?=$invite['id']?>"> |
| | | <td> |
| | | <?=$invite['name']?> |
| | | </td> |
| | | <td> |
| | | <?=$invite['email']?> |
| | | </td> |
| | | <td> |
| | | <?=$invite['status']?> |
| | | </td> |
| | | <td> |
| | | <?=$invite['expires_at']?> |
| | | </td> |
| | | <td class="actions"> |
| | | <button type="button" data-action="revoke" title="Revoke Invitation" onclick="revokeInvite(this)"> |
| | | <?=jvbIcon('trash')?> |
| | | <span class="screen-reader-text">Revoke invite</span> |
| | | </button> |
| | | <button type="button" data-action="resend"<?=($invite['status'] === 'exipired') ? '' : ' disabled'?> title="Resend Invitation" onclick="resendInvite(this)"> |
| | | <?=jvbIcon('arrows-clockwise')?> |
| | | <span class="screen-reader-text">Try Again</span> |
| | | </button> |
| | | </td> |
| | | |
| | | </tr> |
| | | <?php |
| | | } |
| | | } else { |
| | | ?> |
| | | <tr><td><i>No invites yet</i></td><td></td><td></td><td></td><td></td></tr> |
| | | <?php |
| | | } |
| | | ?> |
| | | </tbody> |
| | | </table> |
| | | <?php |
| | | } |
| | | } |
| | | //TODO: Likely replaced with Registrar? |
| | | // |
| | | //class UserSettings extends ContentRegistry |
| | | //{ |
| | | // protected array $fields; |
| | | // protected array $sections = [ |
| | | // 'account' => [ |
| | | // 'title' => 'Account', |
| | | // 'icon' => 'settings', |
| | | // ], |
| | | // 'notifications' => [ |
| | | // 'title' => 'Notifications', |
| | | // 'icon' => 'email' |
| | | // ], |
| | | // 'newsletter' => [ |
| | | // 'title' => 'Newsletter', |
| | | // 'icon' => 'logo' |
| | | // ], |
| | | // 'invitations' => [ |
| | | // 'title' => 'Invite Artists', |
| | | // 'icon' => 'new-user', |
| | | // ] |
| | | // ]; |
| | | // protected array $frequency = [ |
| | | // 'never' => 'Never', |
| | | // 'daily' => 'Daily', |
| | | // 'weekly' => 'Weekly', |
| | | // 'monthly' => 'Monthly', |
| | | // ]; |
| | | // |
| | | // public function initType():void |
| | | // { |
| | | // $this->object_type = 'user'; |
| | | // $this->userFields(); |
| | | // } |
| | | // |
| | | // public function getFields():array |
| | | // { |
| | | // return $this->fields; |
| | | // } |
| | | // public function getSections():array |
| | | // { |
| | | // return $this->sections; |
| | | // } |
| | | // public function userFields():void |
| | | // { |
| | | // |
| | | // $this->fields = [ |
| | | // 'display_name' => [ |
| | | // 'type' => 'text', |
| | | // 'label' => 'Name', |
| | | // 'description' => 'Optional. Only used if you allow artists to see your info when you favourite them.', |
| | | // 'default' => '', |
| | | // 'section' => 'account', |
| | | // 'role' => ['ei_enthusiast'] |
| | | // ], |
| | | // 'user_email' => [ |
| | | // 'type' => 'email', |
| | | // 'label' => 'Email', |
| | | // 'description' => 'Required for any notifications you subscribe to.', |
| | | // 'default' => '', |
| | | // 'section' => 'account', |
| | | // 'role' => ['ei_enthusiast'] |
| | | // ], |
| | | // 'city' => [ |
| | | // 'type' => 'text', |
| | | // 'label' => 'City', |
| | | // 'description' => 'Optional. Just helps us know where people who use this site are located.', |
| | | // 'default' => '', |
| | | // 'section' => 'account', |
| | | // 'role' => ['ei_enthusiast'] |
| | | // ], |
| | | // 'notify' => [ |
| | | // 'type' => 'true_false', |
| | | // 'label' => 'Notify artists when you favourite their work', |
| | | // 'description' => 'If you select this, artists will see your name when you favourite their work, else they will just get an anonymous notification.', |
| | | // 'default' => false, |
| | | // 'section' => 'account', |
| | | // ], |
| | | // 'dark_mode' => [ |
| | | // 'type' => 'true_false', |
| | | // 'label' => 'Dark Mode', |
| | | // 'default' => false, |
| | | // ], |
| | | // 'email_digest' => [ |
| | | // 'type' => 'radio', |
| | | // 'label' => 'Email Summary Frequency', |
| | | // 'options' => $this->frequency, |
| | | // 'default' => 'never', |
| | | // 'section' => 'notifications' |
| | | // ], |
| | | // 'digest_override' => [ |
| | | // 'type' => 'true_false', |
| | | // 'label' => 'Apply to all notifications', |
| | | // 'description' => 'Override individual notification settings and apply this frequency to all your notifications', |
| | | // 'default' => false, |
| | | // 'section' => 'notifications' |
| | | // ], |
| | | // 'notifications' => [ |
| | | // 'type' => 'callback', |
| | | // 'callback' => 'renderNotifications', |
| | | // 'label' => 'Notifications', |
| | | // 'section' => 'notifications', |
| | | // 'default' => '', |
| | | // ], |
| | | // 'site_updates' => [ |
| | | // 'type' => 'true_false', |
| | | // 'label' => 'Site Updates', |
| | | // 'description' => 'Get notified when new features are added to edmonton.ink', |
| | | // 'default' => false, |
| | | // 'section' => 'account', |
| | | // ], |
| | | // 'newsletter' => [ |
| | | // 'type' => 'true_false', |
| | | // 'label' => 'Newsletter', |
| | | // 'description' => 'Sign up for our monthly-ish newsletter, featuring upcoming events, featured artists, new artists, and their work.', |
| | | // 'default' => false, |
| | | // 'section' => 'newsletter', |
| | | // ], |
| | | // 'owner_of' => [ |
| | | // 'type' => 'selector', |
| | | // 'subtype'=> 'taxonomy', |
| | | // 'label' => __('Owner of', 'jvb'), |
| | | // 'isReference' => true, |
| | | // 'taxonomy' => BASE. 'shop', |
| | | // 'quickEdit' => true, |
| | | // 'default' => '', |
| | | // ], |
| | | // 'manager_of' => [ |
| | | // 'type' => 'selector', |
| | | // 'subtype' => 'taxonomy', |
| | | // 'label' => __('Manager of', 'jvb'), |
| | | // 'isReference' => true, |
| | | // 'taxonomy' => BASE. 'shop', |
| | | // 'hidden' => true, |
| | | // 'default' => '', |
| | | // ], |
| | | // |
| | | // 'invitations' => [ |
| | | // 'type' => 'callback', |
| | | // 'callback' => 'handleInvitationsManagement', |
| | | // 'section' => 'invitations', |
| | | // 'label' => __('Invite Artists', 'jvb'), |
| | | // 'role' => ['ei_artist'] |
| | | // ], |
| | | // ]; |
| | | // } |
| | | // |
| | | // /** |
| | | // * Render custom notification preferences UI |
| | | // * @param int $user_id |
| | | // * |
| | | // * @return string |
| | | // */ |
| | | // public function renderNotifications(int $user_id):string |
| | | // { |
| | | // $favourites = $this->getUserFavourites($user_id); |
| | | // $preferences = $this->getUserNotificationPreferences($user_id); |
| | | // global $notify; |
| | | // |
| | | // ob_start(); |
| | | // ?> |
| | | <!-- <div class="notification-preferences">--> |
| | | <!-- --><?php //foreach ($favourites as $type => $items) : ?> |
| | | <!-- <details class="notification-group">--> |
| | | <!-- <summary class="notification-group-header row x-btw">--> |
| | | <!-- <span class="type-label">--><?php //=jvbIcon('heart', ['style' => 'fill'])?><!-- --><?php //= $notify[$type] ?><!--</span>--> |
| | | <!-- <span class="type-count">( --><?php //= count($items); ?><!-- )</span>--> |
| | | <!-- </summary>--> |
| | | <!----> |
| | | <!-- --><?php //if (!empty($items)) : ?> |
| | | <!-- <table class="notification-table">--> |
| | | <!-- <thead>--> |
| | | <!-- <tr>--> |
| | | <!-- <th>Name</th>--> |
| | | <!-- <th>Frequency</th>--> |
| | | <!-- </tr>--> |
| | | <!-- </thead>--> |
| | | <!-- <tbody>--> |
| | | <!-- --><?php //foreach ($items as $item) : ?> |
| | | <!-- --><?php |
| | | // $current = isset($preferences['preferences'][$type][$item['id']]) |
| | | // ? $preferences['preferences'][$type][$item['id']] |
| | | // : $preferences['default_frequency']; |
| | | // ?> |
| | | <!-- <tr>--> |
| | | <!-- <td>--><?php //= esc_html($item['name']); ?><!--</td>--> |
| | | <!-- <td>--> |
| | | <!-- <div class="radio-options row">--> |
| | | <!-- --><?php //foreach (['never', 'daily', 'weekly', 'monthly'] as $freq) : ?> |
| | | <!-- <input type="radio"--> |
| | | <!-- name="notification_preferences[--><?php //= esc_attr($type); ?><!--][--><?php //= esc_attr($item['id']); ?><!--]"--> |
| | | <!-- id="--><?php //= esc_attr("{$type}_{$item['id']}_$freq"); ?><!--"--> |
| | | <!-- value="--><?php //= esc_attr($freq); ?><!--"--> |
| | | <!-- --><?php //checked($current, $freq); ?><!-->--> |
| | | <!-- <label for="--><?php //= esc_attr("{$type}_{$item['id']}_$freq"); ?><!--">--> |
| | | <!-- --><?php //= ucfirst($freq); ?> |
| | | <!-- </label>--> |
| | | <!-- --><?php //endforeach; ?> |
| | | <!-- </div>--> |
| | | <!-- </td>--> |
| | | <!-- </tr>--> |
| | | <!-- --><?php //endforeach; ?> |
| | | <!-- </tbody>--> |
| | | <!-- <tfoot>--> |
| | | <!-- <tr>--> |
| | | <!----> |
| | | <!-- <th>Set all --><?php //= $type ?><!--s to: </th>--> |
| | | <!-- <th>--> |
| | | <!-- <div class="radio-options row">--> |
| | | <!-- --><?php //foreach (['never', 'daily', 'weekly', 'monthly'] as $freq) : ?> |
| | | <!-- <input type="radio"--> |
| | | <!-- name="toggle-all---><?php //=$type?><!--"--> |
| | | <!-- id="toggle-all---><?php //=$type?><!-----><?php //=$freq?><!--"--> |
| | | <!-- value="--><?php //= esc_attr($freq); ?><!--"--> |
| | | <!-- class="toggle-all">--> |
| | | <!-- <label for="toggle-all---><?php //=$type?><!-----><?php //=$freq?><!--">--> |
| | | <!-- --><?php //= ucfirst($freq); ?> |
| | | <!-- </label>--> |
| | | <!-- --><?php //endforeach; ?> |
| | | <!-- </div>--> |
| | | <!-- </th>--> |
| | | <!----> |
| | | <!-- </tr>--> |
| | | <!-- </tfoot>--> |
| | | <!-- </table>--> |
| | | <!-- --><?php //else : ?> |
| | | <!-- <p class="no-items">No favourites yet</p>--> |
| | | <!-- --><?php //endif; ?> |
| | | <!-- </details>--> |
| | | <!-- --><?php //endforeach; ?> |
| | | <!-- </div>--> |
| | | <!-- <script>--> |
| | | <!-- function initGroupOverrides() {--> |
| | | <!-- document.querySelectorAll('.toggle-all').forEach(toggle => {--> |
| | | <!-- toggle.addEventListener('change', e => {--> |
| | | <!-- e.preventDefault();--> |
| | | <!-- if(toggle.checked){--> |
| | | <!-- const table = e.target.closest('table');--> |
| | | <!-- console.log(table);--> |
| | | <!-- const value = toggle.value;--> |
| | | <!----> |
| | | <!-- // Enable/disable individual controls--> |
| | | <!-- table.querySelectorAll('input[value="'+value+'"]').forEach(radio => {--> |
| | | <!-- radio.checked = true;--> |
| | | <!-- });--> |
| | | <!-- }--> |
| | | <!----> |
| | | <!-- });--> |
| | | <!-- });--> |
| | | <!----> |
| | | <!-- // Listen for changes to default frequency--> |
| | | <!-- document.querySelector('input[name="digest_override"]').addEventListener('change', e=>{--> |
| | | <!-- let value = document.querySelector('input[name="email_digest"]:checked').value;--> |
| | | <!----> |
| | | <!-- console.log(value);--> |
| | | <!-- document.querySelectorAll('input[value="'+value+'"]').forEach(toggle => {--> |
| | | <!-- toggle.checked = true;--> |
| | | <!-- });--> |
| | | <!----> |
| | | <!-- setTimeout(function () {--> |
| | | <!-- e.target.checked = false;--> |
| | | <!-- }, 1000)--> |
| | | <!----> |
| | | <!-- });--> |
| | | <!----> |
| | | <!-- }--> |
| | | <!-- initGroupOverrides();--> |
| | | <!-- </script>--> |
| | | <!-- --><?php |
| | | // return ob_get_clean(); |
| | | // } |
| | | // |
| | | // /** |
| | | // * Get user's favourites grouped by type |
| | | // */ |
| | | // protected function getUserFavourites(int $user_id):array |
| | | // { |
| | | // global $wpdb; |
| | | // $table = $wpdb->prefix . BASE.'favourites'; |
| | | // global $notify; |
| | | // global $types; |
| | | // |
| | | // |
| | | // $favourites = []; |
| | | // foreach (array_keys($notify) as $type) { |
| | | // $favourites[$type] = []; |
| | | // } |
| | | // |
| | | // // Fetch favourites from database |
| | | // $results = $wpdb->get_results($wpdb->prepare( |
| | | // "SELECT * FROM $table WHERE user_id = %d", |
| | | // $user_id |
| | | // )); |
| | | // |
| | | // foreach ($results as $result) { |
| | | // $type = str_replace(BASE, '', $result->type); |
| | | // |
| | | // |
| | | // if (!array_key_exists($type, $favourites)) { |
| | | // continue; |
| | | // } |
| | | // // Get proper name based on type |
| | | // switch ($types[BASE.$type]['table']) { |
| | | // case 'post': |
| | | // $name = get_the_title($result->target_id); |
| | | // break; |
| | | // case 'term': |
| | | // $term = get_term($result->target_id, BASE.$type); |
| | | // $name = $term ? html_entity_decode($term->name) : ''; |
| | | // break; |
| | | // default: |
| | | // $name = ''; |
| | | // } |
| | | // |
| | | // if ($name) { |
| | | // $favourites[$type][] = [ |
| | | // 'id' => $result->target_id, |
| | | // 'name' => $name |
| | | // ]; |
| | | // } |
| | | // } |
| | | // |
| | | // return $favourites; |
| | | // } |
| | | // |
| | | // /** |
| | | // * Get current notification preferences |
| | | // */ |
| | | // protected function getNotificationPreferences(int $user_id):array |
| | | // { |
| | | // return get_user_meta($user_id, 'notification_preferences', true) ?: []; |
| | | // } |
| | | // |
| | | // protected function getUserNotificationPreferences(int $user_id):array |
| | | // { |
| | | // global $wpdb; |
| | | // $table = $wpdb->prefix . BASE.'notification_preferences'; |
| | | // |
| | | //// error_log('Getting notification preferences for user: ' . $user_id); |
| | | // |
| | | // // Get user's preferences from the table |
| | | // $raw_preferences = $wpdb->get_results($wpdb->prepare( |
| | | // "SELECT notification_type, item_id, frequency |
| | | // FROM {$table} |
| | | // WHERE user_id = %d", |
| | | // $user_id |
| | | // ), ARRAY_A); |
| | | // |
| | | //// error_log('Raw preferences from database: ' . print_r($raw_preferences, true)); |
| | | // |
| | | // // Structure the preferences |
| | | // $preferences = []; |
| | | // if (!empty($raw_preferences)) { |
| | | // foreach ($raw_preferences as $pref) { |
| | | // $type = $pref['notification_type']; |
| | | // $item_id = intval($pref['item_id']); |
| | | // $frequency = $pref['frequency']; |
| | | // |
| | | // if (!isset($preferences[$type])) { |
| | | // $preferences[$type] = []; |
| | | // } |
| | | // $preferences[$type][$item_id] = $frequency; |
| | | // } |
| | | // } |
| | | // |
| | | // // Get default frequency |
| | | // $default_frequency = get_user_meta($user_id, BASE.'email_digest', true) ?: 'never'; |
| | | // |
| | | //// error_log('Structured preferences: ' . print_r($preferences, true)); |
| | | //// error_log('Default frequency: ' . $default_frequency); |
| | | // |
| | | // return [ |
| | | // 'preferences' => $preferences, |
| | | // 'default_frequency' => $default_frequency |
| | | // ]; |
| | | // } |
| | | // |
| | | // public function handleInvitationsManagement(int $userID):void |
| | | // { |
| | | // $name = 'artist_invite'; |
| | | // ?> |
| | | <!-- <div class="field">--> |
| | | <!-- <script type="text/javascript">--> |
| | | <!-- function addArtist(button){--> |
| | | <!-- let template = window.getTemplate('artistInvite');--> |
| | | <!-- let container = button.closest('.field').querySelector('.invite-artists');--> |
| | | <!----> |
| | | <!-- let nameLabel = template.querySelector('label[for="invited-artist-name"]'),--> |
| | | <!-- nameInput = template.querySelector('input#invited-artist-name'),--> |
| | | <!-- emailLabel = template.querySelector('label[for="invited-artist-email"]'),--> |
| | | <!-- emailInput = template.querySelector('input#invited-artist-email'),--> |
| | | <!-- shopLabel = template.querySelector('label[for="invited-add-to-shop"]'),--> |
| | | <!-- shopInput = template.querySelector('input#invited-add-to-shop');--> |
| | | <!----> |
| | | <!----> |
| | | <!-- let count = container.childNodes.length;--> |
| | | <!-- console.log(container.childNodes);--> |
| | | <!-- [--> |
| | | <!-- nameLabel.htmlFor,--> |
| | | <!-- nameInput.id,--> |
| | | <!-- nameInput.name,--> |
| | | <!-- emailLabel.htmlFor,--> |
| | | <!-- emailInput.id,--> |
| | | <!-- emailInput.name,--> |
| | | <!-- shopLabel.htmlFor,--> |
| | | <!-- shopInput.id,--> |
| | | <!-- shopInput.name--> |
| | | <!-- ] =--> |
| | | <!-- [--> |
| | | <!-- `${nameLabel.getAttribute('for')}-${count}`,--> |
| | | <!-- `${nameInput.id}-${count}`,--> |
| | | <!-- `${nameInput.name}-${count}`,--> |
| | | <!-- `${emailLabel.htmlFor}-${count}`,--> |
| | | <!-- `${emailInput.id}-${count}`,--> |
| | | <!-- `${emailInput.name}-${count}`,--> |
| | | <!-- `${shopLabel.htmlFor}-${count}`,--> |
| | | <!-- `${shopInput.id}-${count}`,--> |
| | | <!-- `${shopInput.name}-${count}`--> |
| | | <!-- ];--> |
| | | <!-- container.append(template);--> |
| | | <!-- }--> |
| | | <!-- function removeArtist(button){--> |
| | | <!-- button.closest('.invited-artist').remove();--> |
| | | <!-- }--> |
| | | <!----> |
| | | <!-- async function revokeInvite(button){--> |
| | | <!-- try {--> |
| | | <!-- let artist = button.closest('tr');--> |
| | | <!-- let id = artist.id;--> |
| | | <!----> |
| | | <!-- const headers = {--> |
| | | <!-- 'X-WP-Nonce': eiSettings.nonce,--> |
| | | <!-- 'action_nonce': eiSettings.dash,--> |
| | | <!-- 'Content-Type': 'application/json'--> |
| | | <!-- };--> |
| | | <!----> |
| | | <!-- let body = {--> |
| | | <!-- user: eiSettings.currentUser,--> |
| | | <!-- revoke: id--> |
| | | <!-- };--> |
| | | <!-- let request = {--> |
| | | <!-- method: 'POST',--> |
| | | <!-- headers: headers,--> |
| | | <!-- body: JSON.stringify(body)--> |
| | | <!-- };--> |
| | | <!-- console.log(request);--> |
| | | <!----> |
| | | <!----> |
| | | <!-- const response = await fetch(`${eiSettings.api}invitations`, request);--> |
| | | <!----> |
| | | <!-- console.log(response);--> |
| | | <!-- if (!response.ok) {--> |
| | | <!-- const errorData = await response.json().catch(() => ({}));--> |
| | | <!-- }--> |
| | | <!----> |
| | | <!-- const data = await response.json();--> |
| | | <!-- if(data.success){--> |
| | | <!-- artist.remove();--> |
| | | <!-- }--> |
| | | <!-- }catch (error) {--> |
| | | <!-- }--> |
| | | <!-- }--> |
| | | <!----> |
| | | <!-- async function resendInvite(button){--> |
| | | <!-- try {--> |
| | | <!-- let artist = button.closest('tr');--> |
| | | <!-- let id = artist.id;--> |
| | | <!----> |
| | | <!-- const headers = {--> |
| | | <!-- 'X-WP-Nonce': eiSettings.nonce,--> |
| | | <!-- 'action_nonce': eiSettings.dash,--> |
| | | <!-- 'Content-Type': 'application/json'--> |
| | | <!-- };--> |
| | | <!----> |
| | | <!-- let body = {--> |
| | | <!-- user: eiSettings.currentUser,--> |
| | | <!-- refresh: id--> |
| | | <!-- };--> |
| | | <!-- let request = {--> |
| | | <!-- method: 'POST',--> |
| | | <!-- headers: headers,--> |
| | | <!-- body: JSON.stringify(body)--> |
| | | <!-- };--> |
| | | <!-- console.log(request);--> |
| | | <!----> |
| | | <!----> |
| | | <!-- const response = await fetch(`${eiSettings.api}invitations`, request);--> |
| | | <!----> |
| | | <!-- console.log(response);--> |
| | | <!-- if (!response.ok) {--> |
| | | <!-- const errorData = await response.json().catch(() => ({}));--> |
| | | <!-- }--> |
| | | <!----> |
| | | <!-- const data = await response.json();--> |
| | | <!----> |
| | | <!-- }catch (error) {--> |
| | | <!-- }--> |
| | | <!-- }--> |
| | | <!----> |
| | | <!-- function sendInvites(button){--> |
| | | <!-- if(document.querySelector('.invited-artist')){--> |
| | | <!-- let newArtists = [];--> |
| | | <!-- document.querySelectorAll('.invited-artist').forEach(invited => {--> |
| | | <!-- let name = invited.querySelector('input[type="text"]'),--> |
| | | <!-- email = invited.querySelector('input[type="email"]'),--> |
| | | <!-- toShop = invited.querySelector('input[type="checkbox"]');--> |
| | | <!-- if(name.value === '' || email.value === ''){--> |
| | | <!-- invited.classList.add('required');--> |
| | | <!-- return;--> |
| | | <!-- }--> |
| | | <!----> |
| | | <!-- newArtists.push({--> |
| | | <!-- name: name.value,--> |
| | | <!-- email: email.value,--> |
| | | <!-- toShop: toShop.checked--> |
| | | <!-- });--> |
| | | <!-- invited.remove();--> |
| | | <!-- });--> |
| | | <!-- if(newArtists.length > 0){--> |
| | | <!-- window.queueManager.addToQueue({--> |
| | | <!-- type: 'invite_artist',--> |
| | | <!-- data: newArtists--> |
| | | <!-- });--> |
| | | <!-- }--> |
| | | <!-- }--> |
| | | <!-- }--> |
| | | <!-- </script>--> |
| | | <!----> |
| | | <!-- <section class="invite-artists">--> |
| | | <!-- <div class="invited-artist">--> |
| | | <!-- <button type="button" onclick="removeArtist(this)" title="Remove Invite">--><?php //=jvbIcon('trash')?><!--</button>--> |
| | | <!-- <div>--> |
| | | <!-- <label for="invited-artist-name-1">First Name</label>--> |
| | | <!-- <input type="text" id="invited-artist-name-1" name="invited-artist-name-1">--> |
| | | <!-- </div>--> |
| | | <!-- <div>--> |
| | | <!-- <label for="invited-artist-email-1">Email</label>--> |
| | | <!-- <input type="email" id="invited-artist-email-1" name="invited-artist-email-1">--> |
| | | <!-- </div>--> |
| | | <!-- <div>--> |
| | | <!-- <input type="checkbox" name="invited-add-to-shop-1" id="invited-add-to-shop-1">--> |
| | | <!-- <label for="invited-add-to-shop-1">Add to shop?</label>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </section>--> |
| | | <!-- <div class="actions">--> |
| | | <!-- <button type="button" onclick="addArtist(this)" class="add-artist">--><?php //= jvbIcon('plus-square')?><!--Add Artist</button>--> |
| | | <!-- <button type="button" onclick="sendInvites(this)" class="send-invites">--><?php //=jvbIcon('envelope')?><!--Send Invites</button>--> |
| | | <!-- </div>--> |
| | | <!----> |
| | | <!-- <template class="artistInvite">--> |
| | | <!-- <div class="invited-artist">--> |
| | | <!-- <button type="button" onclick="removeArtist(this)" title="Remove Invite">--><?php //=jvbIcon('trash')?><!--</button>--> |
| | | <!-- <div>--> |
| | | <!-- <label for="invited-artist-name">First Name</label>--> |
| | | <!-- <input type="text" id="invited-artist-name" name="invited-artist-name">--> |
| | | <!-- </div>--> |
| | | <!-- <div>--> |
| | | <!-- <label for="invited-artist-email">Email</label>--> |
| | | <!-- <input type="email" id="invited-artist-email" name="invited-artist-email">--> |
| | | <!-- </div>--> |
| | | <!-- <div>--> |
| | | <!-- <input type="checkbox" name="invited-add-to-shop" id="invited-add-to-shop">--> |
| | | <!-- <label for="invited-add-to-shop">Add to shop?</label>--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <!-- </template>--> |
| | | <!-- </div>--> |
| | | <!-- --><?php |
| | | // |
| | | // $invites = JVB()->routes('invites')->getUserInvitations($userID); |
| | | // |
| | | // ?> |
| | | <!-- <table class="artist-invite">--> |
| | | <!-- <thead>--> |
| | | <!-- <tr>--> |
| | | <!-- <th scope="col" style="width:100%;">Name</th>--> |
| | | <!-- <th scope="col" style="padding:0 .5rem">Email</th>--> |
| | | <!-- <th scope="col" style="padding:0 .5rem">Status</th>--> |
| | | <!-- <th scope="col" style="padding:0 .5rem">Expires</th>--> |
| | | <!-- <th scope="col" style="padding:0 .5rem">Actions</th>--> |
| | | <!-- </tr>--> |
| | | <!-- </thead>--> |
| | | <!-- <tbody>--> |
| | | <!-- --><?php |
| | | // if (!empty($invites->invitations)) { |
| | | // foreach ($invites->invitations as $invite) { |
| | | // ?> |
| | | <!-- <tr id="--><?php //=$invite['id']?><!--">--> |
| | | <!-- <td>--> |
| | | <!-- --><?php //=$invite['name']?> |
| | | <!-- </td>--> |
| | | <!-- <td>--> |
| | | <!-- --><?php //=$invite['email']?> |
| | | <!-- </td>--> |
| | | <!-- <td>--> |
| | | <!-- --><?php //=$invite['status']?> |
| | | <!-- </td>--> |
| | | <!-- <td>--> |
| | | <!-- --><?php //=$invite['expires_at']?> |
| | | <!-- </td>--> |
| | | <!-- <td class="actions">--> |
| | | <!-- <button type="button" data-action="revoke" title="Revoke Invitation" onclick="revokeInvite(this)">--> |
| | | <!-- --><?php //=jvbIcon('trash')?> |
| | | <!-- <span class="screen-reader-text">Revoke invite</span>--> |
| | | <!-- </button>--> |
| | | <!-- <button type="button" data-action="resend"--><?php //=($invite['status'] === 'exipired') ? '' : ' disabled'?><!-- title="Resend Invitation" onclick="resendInvite(this)">--> |
| | | <!-- --><?php //=jvbIcon('arrows-clockwise')?> |
| | | <!-- <span class="screen-reader-text">Try Again</span>--> |
| | | <!-- </button>--> |
| | | <!-- </td>--> |
| | | <!----> |
| | | <!-- </tr>--> |
| | | <!-- --><?php |
| | | // } |
| | | // } else { |
| | | // ?> |
| | | <!-- <tr><td><i>No invites yet</i></td><td></td><td></td><td></td><td></td></tr>--> |
| | | <!-- --><?php |
| | | // } |
| | | // ?> |
| | | <!-- </tbody>--> |
| | | <!-- </table>--> |
| | | <!-- --><?php |
| | | // } |
| | | //} |
| | |
| | | |
| | | public function getImageData(int $imgID):array |
| | | { |
| | | return $this->imgData->remember( |
| | | return $this->vidData->remember( |
| | | $imgID, |
| | | function() use ($imgID) { |
| | | if (!wp_get_attachment_image($imgID, 'tiny')) { |
| | |
| | | use JVBase\managers\Login; |
| | | use JVBase\managers\MagicLinks; |
| | | use JVBase\managers\NotificationManager; |
| | | use JVBase\managers\Notifications\Notifications; |
| | | use JVBase\managers\queue\Queue; |
| | | use JVBase\managers\Referrals; |
| | | use JVBase\managers\Responses; |
| | |
| | | define('JVB_URL', plugin_dir_url(__FILE__)); |
| | | |
| | | // Session Security |
| | | define('JVB_SESSION_FINGERPRINT', true); |
| | | const JVB_SESSION_FINGERPRINT = true; |
| | | |
| | | // Login Security |
| | | define('JVB_MAX_LOGIN_ATTEMPTS', 5); |
| | | define('JVB_LOCKOUT_DURATION', 15 * MINUTE_IN_SECONDS); |
| | | const JVB_MAX_LOGIN_ATTEMPTS = 5; |
| | | const JVB_LOCKOUT_DURATION = 15 * MINUTE_IN_SECONDS; |
| | | |
| | | |
| | | require(JVB_DIR.'/inc/Instance.php'); |
| | |
| | | function jvbImageSize():void |
| | | { |
| | | add_theme_support('post-thumbnails'); |
| | | add_image_size( 'tiny', 50, 50, false ); |
| | | add_image_size( 'directory-preview', 100, 100, false ); |
| | | add_image_size( 'tiny', 50, 50 ); |
| | | add_image_size( 'directory-preview', 100, 100 ); |
| | | } |
| | | |
| | | |
| | |
| | | <?php |
| | | |
| | | use JVBase\meta\Form; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | | } |
| | |
| | | |
| | | // Buffer output |
| | | if (is_tax()) { |
| | | switch (get_queried_object()->taxonomy) { |
| | | case BASE.'shop': |
| | | return jvbRenderShopSummary(); |
| | | default: |
| | | return jvbRenderTermSummary(); |
| | | } |
| | | return match (get_queried_object()->taxonomy) { |
| | | BASE . 'shop' => jvbRenderShopSummary(), |
| | | default => jvbRenderTermSummary(), |
| | | }; |
| | | } elseif (is_singular()) { |
| | | return jvbRenderArtistSummary(); |
| | | } |
| | |
| | | <h2>Contact <?=$artist['name']?></h2> |
| | | <?php |
| | | echo jvbRenderContactInfo($current->ID, 'post'); |
| | | echo jvbRenderLinks($current->ID, 'post'); |
| | | echo jvbRenderLinks($current->ID); |
| | | ?> |
| | | </section> |
| | | <?php |
| | |
| | | |
| | | ob_start(); |
| | | $tax = jvbNoBase($current->taxonomy); |
| | | switch ($tax) { |
| | | case 'style': |
| | | $title = 'Tattoo Artists'; |
| | | break; |
| | | case 'theme': |
| | | $title = 'Tattoos'; |
| | | break; |
| | | default: |
| | | $title = ''; |
| | | } |
| | | $title = match ($tax) { |
| | | 'style' => 'Tattoo Artists', |
| | | 'theme' => 'Tattoos', |
| | | default => '', |
| | | }; |
| | | |
| | | $meta = Meta::forTerm($current->ID); |
| | | $fields = $meta->getAll(); |
| | |
| | | foreach ($shops as $shop) { |
| | | $active = ($i=== 0) ? ' active' : ''; |
| | | echo '<section class="tab-content'.$active.'" data-tab="shop-'.$shop->term_id.'" role="tabpanel">'; |
| | | jvbRenderSections($handler, $shop->term_id, 'term', BASE.'shop', true); |
| | | jvbRenderSections($handler, $shop->term_id, 'term', BASE.'shop'); |
| | | echo '</section>'; |
| | | $i++; |
| | | } |
| | |
| | | <div class="section"> |
| | | <h2 class="section-title"> |
| | | <?php |
| | | switch($type) { |
| | | case 'artist_new_tattoo': |
| | | echo "FRESH INK ↯"; |
| | | break; |
| | | case 'artist_new_artwork': |
| | | echo "NEW ART DROPS ⚡"; |
| | | break; |
| | | case 'shop_update': |
| | | echo "SHOP TALK"; |
| | | break; |
| | | default: |
| | | echo "SCENE NEWS"; |
| | | } |
| | | echo match ($type) { |
| | | 'artist_new_tattoo' => "FRESH INK ↯", |
| | | 'artist_new_artwork' => "NEW ART DROPS ⚡", |
| | | 'shop_update' => "SHOP TALK", |
| | | default => "SCENE NEWS", |
| | | }; |
| | | ?> |
| | | </h2> |
| | | |