PluginProbe
UpStream: a Project Management Plugin for WordPress / 1.39.0
UpStream: a Project Management Plugin for WordPress v1.39.0
trunk 1.39.0 1.39.1 1.39.2 1.39.3 2.0.7 2.1.0
upstream / upstream.php

upstream.php in UpStream: a Project Management Plugin for WordPress 1.39.0, at upstream.php

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