PluginProbe
UpStream: a Project Management Plugin for WordPress / 2.1.0
UpStream: a Project Management Plugin for WordPress v2.1.0
trunk 1.39.0 1.39.1 1.39.2 1.39.3 2.0.7 2.1.0
upstream / class-upstream.php

class-upstream.php in UpStream: a Project Management Plugin for WordPress 2.1.0, at class-upstream.php

601 lines 18.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Main UpStream Class.
4 *
5 * @package UpStream
6 */
7
8 use UpStream\Container;
9 use UpStream\Comments;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 if ( ! class_exists( 'UpStream' ) ) :
16
17 /**
18 * Main UpStream Class.
19 *
20 * @since 1.0.0
21 */
22 final class UpStream {
23
24 /**
25 * The one true UpStream
26 *
27 * @var UpStream
28 * @since 1.0.0
29 */
30 protected static $instance = null;
31
32 /**
33 * Twig Environment
34 *
35 * @var Twig_Environment
36 */
37 protected $twig;
38
39 /**
40 * Container
41 *
42 * @var Container
43 */
44 protected $container;
45
46 /**
47 * Main UpStream Instance.
48 */
49 public static function instance() {
50 if ( is_null( self::$instance ) ) {
51 self::$instance = new self();
52 }
53
54 return self::$instance;
55 }
56
57 /**
58 * Throw error on object clone.
59 *
60 * The whole idea of the singleton design pattern is that there is a single
61 * object therefore, we don't want the object to be cloned.
62 *
63 * @since 1.0.0
64 */
65 public function __clone() {
66 _doing_it_wrong( __FUNCTION__, 'You\'re not supposed to clone this class.', esc_html( UPSTREAM_VERSION ) );
67 }
68
69 /**
70 * Disable unserializing of the class.
71 *
72 * @since 1.0.0
73 */
74 public function __wakeup() {
75 _doing_it_wrong( __FUNCTION__, 'You\'re not supposed to unserialize this class.', esc_html( UPSTREAM_VERSION ) );
76 }
77
78 /**
79 * Prevent the class instance being serialized.
80 *
81 * @since 1.10.2
82 */
83 public function __sleep() {
84 _doing_it_wrong( __FUNCTION__, 'You\'re not supposed to serialize this class.', esc_html( UPSTREAM_VERSION ) );
85 }
86
87 /**
88 * Class constructor.
89 */
90 public function __construct() {
91 $this->define_constants();
92 $this->includes();
93 $this->container = Container::get_instance();
94 $this->init_framework();
95
96 if ( UpStream_Debug::is_enabled() ) {
97 UpStream_Debug::init();
98 }
99
100 $this->init_hooks();
101
102 do_action( 'upstream_loaded' );
103 }
104
105 /**
106 * Hook into actions and filters.
107 *
108 * @since 1.0.0
109 */
110 private function init_hooks() {
111 add_action( 'init', array( $this, 'init' ) );
112 add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 2 );
113 add_filter( 'plugin_action_links_upstream/upstream.php', array( $this, 'handle_action_links' ) );
114 add_filter( 'http_request_host_is_external', array( 'UpStream', 'allow_external_update_host' ), 10, 3 );
115 add_filter( 'quicktags_settings', 'upstream_tinymce_quicktags_settings' );
116 add_filter( 'tiny_mce_before_init', 'upstream_tinymce_before_init_setup_toolbar' );
117 add_filter( 'tiny_mce_before_init', 'upstream_tinymce_before_init' );
118 add_filter( 'teeny_mce_before_init', 'upstream_tinymce_before_init_setup_toolbar' );
119 add_filter( 'comments_clauses', array( $this, 'filter_comments_on_dashboard' ), 10, 2 );
120 add_filter( 'views_dashboard', array( 'UpStream_Admin', 'comment_status_links' ), 10, 1 );
121 add_action( 'plugins_loaded', array( $this, 'load_plugin_textdomain' ) );
122
123 if ( is_admin() ) {
124 add_action( 'admin_init', array( $this->container['reviews'], 'init' ) );
125 }
126
127 global $pagenow;
128
129 if ( 'plugins.php' === $pagenow ) {
130 add_action(
131 'in_plugin_update_message-' . UPSTREAM_PLUGIN_BASENAME,
132 array( $this, 'render_additional_update_info' ),
133 20,
134 2
135 );
136 }
137 }
138
139 /**
140 * Initialize the Alledia Framework.
141 */
142 private function init_framework() {
143 $this->container['framework']->init();
144 }
145
146
147 /**
148 * Prevent a Client User from accessing any page other than the profile.
149 *
150 * @since 1.11.0
151 *
152 * @global $pagenow
153 */
154 public function limit_client_users_admin_access() {
155 global $pagenow;
156
157 $profile_age = 'profile.php';
158 if ( $pagenow !== $profile_age && 'edit.php' !== $pagenow && ! wp_doing_ajax() ) {
159 wp_safe_redirect( admin_url( $profile_age ) );
160 exit();
161 }
162 }
163
164 /**
165 * Make sure Client Users can only see the Profile menu item.
166 *
167 * @since 1.11.0
168 *
169 * @global $menu
170 */
171 public function limit_client_users_menu() {
172 global $menu;
173
174 foreach ( $menu as $menu_index => $menu_data ) {
175 $menu_file = isset( $menu_data[2] ) ? $menu_data[2] : null;
176
177 if ( null !== $menu_file ) {
178 if ( 'profile.php' === $menu_file || 'edit.php?post_type=project' === $menu_file ) {
179 continue;
180 }
181
182 remove_menu_page( $menu_file );
183 }
184 }
185 }
186
187 /**
188 * Hide some toolbar items from Client Users.
189 *
190 * @param \WP_Admin_Bar $wp_admin_bar WordPress admin bar.
191 *
192 * @since 1.11.0
193 */
194 public function limit_client_users_toolbar_items( $wp_admin_bar ) {
195 $user = wp_get_current_user();
196 $user_roles = (array) $user->roles;
197
198 if ( count( array_intersect( $user_roles, array( 'administrator', 'upstream_manager' ) ) ) === 0
199 && in_array( 'upstream_client_user', $user_roles, true )
200 ) {
201 $menu_items = array( 'about', 'comments', 'new-content' );
202
203 if ( ! is_admin() ) {
204 $menu_items = array_merge( $menu_items, array( 'dashboard', 'edit' ) );
205 }
206
207 foreach ( $menu_items as $menu_item ) {
208 $wp_admin_bar->remove_menu( $menu_item );
209 }
210 }
211 }
212
213 /**
214 * Get container.
215 *
216 * @return Container
217 */
218 public function get_container() {
219 return $this->container;
220 }
221
222 /**
223 * Define Constants.
224 *
225 * @since 1.0.0
226 */
227 private function define_constants() {
228 $upload_dir = wp_upload_dir();
229
230 $this->define( 'UPSTREAM_PLUGIN_DIR', plugin_dir_path( UPSTREAM_PLUGIN_FILE ) );
231 $this->define( 'UPSTREAM_PLUGIN_URL', plugin_dir_url( UPSTREAM_PLUGIN_FILE ) );
232 $this->define( 'UPSTREAM_PLUGIN_BASENAME', plugin_basename( UPSTREAM_PLUGIN_FILE ) );
233 $this->define( 'UPSTREAM_PLUGIN_RELATIVE_PATH', 'upstream' );
234
235 include_once __DIR__ . '/includes.php';
236 }
237
238 /**
239 * Define constant if not already set.
240 *
241 * @param string $name Definition name.
242 * @param string|bool $value Definition value.
243 *
244 * @since 1.0.0
245 */
246 private function define( $name, $value ) {
247 if ( ! defined( $name ) ) {
248 define( $name, $value );
249 }
250 }
251
252 /**
253 * What type of request is this?
254 * string $type frontend or admin.
255 *
256 * @param string $type Request type.
257 * @return bool
258 * @since 1.0.0
259 */
260 private function is_request( $type ) {
261 switch ( $type ) {
262 case 'admin':
263 return is_admin();
264 case 'frontend':
265 return ( ! is_admin() || defined( 'DOING_AJAX' ) ) && ! defined( 'DOING_CRON' );
266 }
267 }
268
269 /**
270 * Include required core files used in admin and on the frontend.
271 *
272 * @since 1.0.0
273 */
274 public function includes() {
275 if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) {
276 require_once __DIR__ . '/vendor/autoload.php';
277 }
278
279 include_once __DIR__ . '/includes/class-exception.php';
280 include_once __DIR__ . '/includes/trait-up-singleton.php';
281 include_once __DIR__ . '/includes/trait-up-post-metadata.php';
282 include_once __DIR__ . '/includes/class-struct.php';
283 include_once __DIR__ . '/includes/class-upstream-debug.php';
284 include_once __DIR__ . '/includes/class-container.php';
285 include_once __DIR__ . '/includes/up-install.php';
286 include_once __DIR__ . '/includes/class-upstream-autoloader.php';
287 include_once __DIR__ . '/includes/class-upstream-roles.php';
288 include_once __DIR__ . '/includes/class-upstream-counts.php';
289 include_once __DIR__ . '/includes/class-upstream-counter.php';
290 include_once __DIR__ . '/includes/class-upstream-project-activity.php';
291 include_once __DIR__ . '/includes/up-permalinks.php';
292 include_once __DIR__ . '/includes/up-general-functions.php';
293 include_once __DIR__ . '/includes/up-post-types.php';
294 include_once __DIR__ . '/includes/up-labels.php';
295 include_once __DIR__ . '/includes/class-milestones.php';
296 include_once __DIR__ . '/includes/class-milestone.php';
297 include_once __DIR__ . '/includes/class-factory.php';
298 include_once __DIR__ . '/includes/up-install.php';
299 include_once __DIR__ . '/includes/up-filesystem.php';
300 include_once __DIR__ . '/includes/up-register-nonce-fields.php';
301 include_once __DIR__ . '/includes/up-wcs-helper.php';
302 include_once __DIR__ . '/includes/class-license-checker.php';
303 include_once __DIR__ . '/includes/up-cron-license-checker.php';
304
305 $request = wp_unslash( $_REQUEST );
306
307 if ( $this->is_request( 'admin' ) ) {
308 global $pagenow;
309
310 $is_multisite = (bool) is_multisite();
311 $load_cmb2 = false;
312 $server = wp_unslash( $_SERVER );
313
314 if ( $is_multisite ) {
315 $current_page = isset( $_SERVER['PHP_SELF'] ) ? preg_replace(
316 '/^\/wp-admin\//i',
317 '',
318 sanitize_text_field( $server['PHP_SELF'] )
319 ) : '';
320 } else {
321 $current_page = (string) $pagenow;
322 }
323
324 if ( in_array( $current_page, array( 'post.php', 'post-new.php' ), true ) ) {
325 $post_type = isset( $request['post_type'] ) ? sanitize_text_field( $request['post_type'] ) : null;
326
327 if ( empty( $post_type ) ) {
328 $project_id = isset( $request['post'] ) ? absint( $request['post'] ) : 0;
329 $post_type = get_post_type( $project_id );
330 }
331
332 if ( ! empty( $post_type ) ) {
333 $post_types_using_cmb2 = apply_filters( 'upstream:post_types_using_cmb2', array( 'project', 'client' ) );
334 $load_cmb2 = in_array( $post_type, $post_types_using_cmb2, true );
335 }
336 } elseif (
337 'admin.php' === $current_page
338 && isset( $request['page'] )
339 && preg_match( '/^upstream_/i', sanitize_text_field( $request['page'] ) )
340 ) {
341 $load_cmb2 = true;
342 }
343
344 if ( $load_cmb2 ) {
345 include_once __DIR__ . '/includes/libraries/cmb2/init.php';
346 include_once __DIR__ . '/includes/libraries/cmb2-grid/Cmb2GridPlugin.php';
347 }
348
349 include_once __DIR__ . '/includes/admin/class-upstream-admin.php';
350 include_once __DIR__ . '/includes/admin/class-upstream-admin-tasks-page.php';
351 include_once __DIR__ . '/includes/admin/class-upstream-admin-bugs-page.php';
352 include_once __DIR__ . '/includes/admin/class-upstream-admin-reviews.php';
353 }
354
355 if ( $this->is_request( 'frontend' ) ) {
356 include_once __DIR__ . '/includes/frontend/class-upstream-template-loader.php';
357 include_once __DIR__ . '/includes/frontend/class-upstream-login.php';
358 include_once __DIR__ . '/includes/frontend/class-upstream-style-output.php';
359 include_once __DIR__ . '/includes/frontend/up-enqueues.php';
360 include_once __DIR__ . '/includes/frontend/up-template-functions.php';
361 include_once __DIR__ . '/includes/frontend/up-table-functions.php';
362 include_once __DIR__ . '/includes/frontend/class-upstream-view.php';
363 include_once __DIR__ . '/includes/frontend/class-upstream-ajax.php';
364 }
365
366 include_once __DIR__ . '/includes/up-project-functions.php';
367 include_once __DIR__ . '/includes/up-client-functions.php';
368 include_once __DIR__ . '/includes/up-permissions-functions.php';
369 include_once __DIR__ . '/includes/class-comments-migration.php';
370 include_once __DIR__ . '/includes/class-comments.php';
371 include_once __DIR__ . '/includes/class-comment.php';
372 }
373
374 /**
375 * Init UpStream when WordPress Initialises.
376 */
377 public function init() {
378 UpStream\Milestones::instantiate();
379
380 do_action( 'before_upstream_init' );
381
382 $this->project = new UpStream_Project();
383 $this->project_activity = new UpStream_Project_Activity();
384
385 if ( version_compare( PHP_VERSION, '5.5', '<' ) ) {
386 require_once UPSTREAM_PLUGIN_DIR . 'includes/libraries/password_compat-1.0.4/lib/password.php';
387 }
388
389 \UpStream\Migrations\Comments_Migration::run();
390
391 $user = wp_get_current_user();
392 $user_roles = (array) $user->roles;
393
394 if (
395 count( array_intersect( $user_roles, array( 'administrator', 'upstream_manager' ) ) ) === 0
396 && in_array( 'upstream_client_user', $user_roles, true )
397 ) {
398 add_filter( 'admin_init', array( $this, 'limit_client_users_admin_access' ) );
399 add_filter( 'admin_head', array( $this, 'limit_client_users_menu' ) );
400 add_action( 'admin_bar_menu', array( $this, 'limit_client_users_toolbar_items' ), 999 );
401 }
402
403 $edit_other_projects_permission_were_removed = (bool) get_option( 'upstream:role_upstream_users:drop_edit_others_projects' );
404
405 if ( ! $edit_other_projects_permission_were_removed ) {
406 $role = get_role( 'upstream_user' );
407
408 if ( $role ) {
409 $role->remove_cap( 'edit_others_projects' );
410 }
411
412 unset( $role );
413 update_option( 'upstream:role_upstream_users:drop_edit_others_projects', 1 );
414 }
415
416 UpStream_Options_Projects::create_projects_statuses_ids();
417 UpStream_Options_Tasks::create_tasks_statuses_ids();
418 UpStream_Options_Bugs::create_bugs_statuses_ids();
419
420 Comments::instantiate();
421
422 if ( $this->is_request( 'frontend' ) ) {
423 UpStream_Ajax::instantiate();
424 }
425
426 do_action( 'upstream_init' );
427 }
428
429 /**
430 * Load Localisation files.
431 */
432 public function load_plugin_textdomain() {
433 load_plugin_textdomain( 'upstream', false, UPSTREAM_PLUGIN_RELATIVE_PATH . '/languages/' );
434 }
435
436
437 /**
438 * Show row meta on the plugin screen.
439 *
440 * @param mixed $links Plugin Row Meta.
441 * @param mixed $file Plugin Base file.
442 *
443 * @return array
444 */
445 public function plugin_row_meta( $links, $file ) {
446 if ( UPSTREAM_PLUGIN_BASENAME === $file ) {
447 $row_meta = array(
448 'docs' => sprintf(
449 '<a href="%s" title="%s">%s</a>',
450 esc_url( 'http://upstreamplugin.com/documentation' ),
451 esc_attr__( 'View Documentation', 'upstream' ),
452 esc_html__( 'Docs', 'upstream' )
453 ),
454 'quick-start' => sprintf(
455 '<a href="%s" title="%s">%s</a>',
456 esc_url( 'http://upstreamplugin.com/quick-start-guide' ),
457 esc_attr__( 'View Quick Start Guide', 'upstream' ),
458 esc_html__( 'Quick Start Guide', 'upstream' )
459 ),
460 );
461
462 return array_merge( $links, $row_meta );
463 }
464
465 return (array) $links;
466 }
467
468 /**
469 * Callback called to setup the links to display on the plugins page, besides active/deactivate links.
470 *
471 * @param array $links The list of links to be displayed.
472 *
473 * @return array
474 * @since 1.11.1
475 * @static
476 */
477 public static function handle_action_links( $links ) {
478 $links['settings'] = sprintf(
479 '<a href="%s" title="%2$s" aria-label="%2$s">%3$s</a>',
480 esc_url( admin_url( 'admin.php?page=upstream_general' ) ),
481 esc_attr__( 'Open Settings Page', 'upstream' ),
482 esc_html__( 'Settings', 'upstream' )
483 );
484
485 return $links;
486 }
487
488 /**
489 * Ensures the plugins update API's host is whitelisted to WordPress external requests.
490 *
491 * @param boolean $is_allowed Is allowed or not.
492 * @param string $host Host.
493 * @param string $url Url.
494 *
495 * @return boolean
496 * @since 1.11.1
497 * @static
498 */
499 public static function allow_external_update_host( $is_allowed, $host, $url ) {
500 if ( 'upstreamplugin.com' === $host ) {
501 return true;
502 }
503
504 return $is_allowed;
505 }
506
507 /**
508 * Render additional update info if needed.
509 *
510 * @param array $plugin_data Plugin metadata.
511 * @param object $response Metadata about the available plugin update.
512 *
513 * @since 1.12.5
514 * @static
515 *
516 * @see https://developer.wordpress.org/reference/hooks/in_plugin_update_message-file
517 */
518 public static function render_additional_update_info( $plugin_data, $response ) {
519 $update_notice_title_html = sprintf(
520 '<strong style="font-size: 1.25em; display: block; margin-top: 10px;">%s</strong>',
521 esc_html__( 'Update notice:', 'upstream' )
522 );
523
524 if ( version_compare( UPSTREAM_VERSION, '1.12.5', '<' ) ) {
525 printf(
526 esc_html( $update_notice_title_html ) .
527 // translators: '%1$s: plugin version, %2$s: capability name, %3$s: UpStream User role'.
528 esc_html__(
529 'Starting from <strong>%1$s</strong> <code>%2$s</code> capability was removed from <code>%3$s</code> users role.',
530 'upstream'
531 ),
532 'v1.12.5',
533 'edit_others_projects',
534 esc_html__( 'UpStream User', 'upstream' )
535 );
536 }
537 }
538
539 /**
540 * Make sure Recent Comments section on admin Dashboard display only comments
541 * current user is allowed to see from projects he's allowed to access.
542 *
543 * @param array $query_args Query clauses.
544 * @param WP_Comment_Query $query Current query instance.
545 *
546 * @return array $queryArgs
547 * @global $pagenow, $wpdb
548 *
549 * @since 1.13.0
550 * @static
551 */
552 public static function filter_comments_on_dashboard( $query_args, $query ) {
553 global $pagenow;
554
555 if ( is_admin() && 'index.php' === $pagenow && ! upstream_is_user_either_manager_or_admin() ) {
556 global $wpdb;
557
558 $query_args['join'] = 'LEFT JOIN ' . $wpdb->prefix . 'posts AS post ON post.ID = ' . $wpdb->prefix . 'comments.comment_post_ID';
559 $user = wp_get_current_user();
560
561 if ( in_array( 'upstream_user', $user->roles, true ) || in_array( 'upstream_client_user', $user->roles, true ) ) {
562 $projects = (array) upstream_get_users_projects( $user );
563
564 if ( count( $projects ) === 0 ) {
565 $query_args['where'] = '(post.ID = -1)';
566 } else {
567 $query_args['where'] = "(post.post_type = 'project' AND post.ID IN (" . implode( ', ', array_keys( $projects ) ) . '))';
568 $user_can_moderate_comments = user_can( $user, 'moderate_comments' );
569
570 if ( ! $user_can_moderate_comments ) {
571 $query_args['where'] .= " AND ( comment_approved = '1' )";
572 } else {
573 $query_args['where'] .= " AND ( comment_approved = '1' OR comment_approved = '0' )";
574 }
575 }
576 } else {
577 $query_args['where'] .= " AND (post.post_type != 'project')";
578 }
579 }
580
581 return $query_args;
582 }
583 }
584 endif;
585
586
587 /**
588 * Main instance of UpStream.
589 *
590 * Returns the main instance of UpStream to prevent the need to use globals.
591 *
592 * @return UpStream
593 * @since 1.0.0
594 */
595 function upstream() {
596 return UpStream::instance();
597 }
598
599 upstream();
600 do_action( 'upstream_run' );
601